001/*
002 *  Copyright 2018 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.runtime.model;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Objects;
025
026import org.apache.cocoon.ProcessingException;
027import org.apache.cocoon.xml.AttributesImpl;
028import org.apache.commons.lang3.StringUtils;
029import org.xml.sax.ContentHandler;
030import org.xml.sax.SAXException;
031
032import org.ametys.core.util.XMLUtils;
033import org.ametys.runtime.i18n.I18nizableText;
034import org.ametys.runtime.model.exception.BadItemTypeException;
035import org.ametys.runtime.util.ModifiableLabelable;
036
037/**
038 * View of items
039 */
040public class View implements ViewItemContainer, ModifiableLabelable
041{
042    private String _name;
043    private I18nizableText _label;
044    private I18nizableText _description;
045    private String _iconGlyph;
046    private String _iconDecorator;
047    private String _smallIcon;
048    private String _mediumIcon;
049    private String _largeIcon;
050    private boolean _isInternal;
051    
052    private List<ViewItem> _items = new ArrayList<>();
053    
054    /**
055     * Creates a {@link View} with the items of the given {@link Model}
056     * @param model the model
057     * @return the created {@link View}
058     * @throws IllegalArgumentException if the model is <code>null</code>
059     */
060    public static View of(Model model) throws IllegalArgumentException
061    {
062        if (model == null)
063        {
064            throw new IllegalArgumentException("Unable to create the view from a null model");
065        }
066        else
067        {
068            return ViewHelper.createViewItemAccessor(List.of(model));
069        }
070    }
071    
072    /**
073     * Creates a {@link View} with the items of the given {@link Model}s
074     * @param models the models
075     * @return the created {@link View}
076     * @throws IllegalArgumentException if the models collection is empty
077     */
078    public static View of(Collection<? extends Model> models) throws IllegalArgumentException
079    {
080        return ViewHelper.createViewItemAccessor(models);
081    }
082    
083    /**
084     * Creates a {@link View} with the given items
085     * @param model the model containing items definitions
086     * @param itemPaths the paths of the items to put in the view
087     * @return the created {@link View}
088     * @throws IllegalArgumentException if the model is <code>null</code> or if an item path is <code>null</code>, empty, or is not defined in the given models
089     * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item
090     */
091    public static View of(Model model, String... itemPaths) throws IllegalArgumentException, BadItemTypeException
092    {
093        if (model == null)
094        {
095            throw new IllegalArgumentException("Unable to create the view from a null model");
096        }
097        else
098        {
099            return ViewHelper.createViewItemAccessor(List.of(model), itemPaths);
100        }
101    }
102    
103    /**
104     * Creates a {@link View} with the given items
105     * @param models the models containing items definitions
106     * @param itemPaths the paths of the items to put in the view
107     * @return the created {@link View}
108     * @throws IllegalArgumentException if the models collection is empty or if an item path is <code>null</code>, empty, or is not defined in the given models
109     * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item
110     */
111    public static View of(Collection<? extends Model> models, String... itemPaths) throws IllegalArgumentException, BadItemTypeException
112    {
113        return ViewHelper.createViewItemAccessor(models, itemPaths);
114    }
115    
116    /**
117     * Creates a {@link View} with the given items. If the items are in a group, the hierarchy will be kept and the corresponding containers will be created
118     * @param modelItems the items to put in the view
119     * @return the created {@link View}
120     */
121    public static View of(ModelItem... modelItems)
122    {
123        View view = new View();
124        
125        for (ModelItem modelItem : modelItems)
126        {
127            ViewHelper.addViewItem(modelItem.getPath(), view, modelItem.getModel());
128        }
129        
130        return view;
131    }
132    
133    /**
134     * Copy the current view in the given one.
135     * Its view items are not copied
136     * @param view the copy
137     */
138    public void copyTo(View view)
139    {
140        view.setName(getName());
141        view.setLabel(getLabel());
142        view.setDescription(getDescription());
143        view.setInternal(isInternal());
144        
145        view.setIconGlyph(getIconGlyph());
146        view.setIconDecorator(getIconDecorator());
147        view.setSmallIcon(getSmallIcon());
148        view.setMediumIcon(getMediumIcon());
149        view.setLargeIcon(getLargeIcon());
150    }
151
152    public String getName()
153    {
154        return _name;
155    }
156
157    public void setName(String name)
158    {
159        _name = name;
160    }
161    
162    public I18nizableText getLabel()
163    {
164        return _label;
165    }
166
167    public void setLabel(I18nizableText label)
168    {
169        _label = label;
170    }
171    
172    public I18nizableText getDescription()
173    {
174        return _description;
175    }
176    
177    public void setDescription(I18nizableText description)
178    {
179        _description = description;
180    }
181    
182    /**
183     * Retrieves the CSS class to use for glyph icon
184     * @return the glyph name.
185     */
186    public String getIconGlyph()
187    {
188        return _iconGlyph;
189    }
190    
191    /**
192     * Set the CSS class to use for glyph icon
193     * @param iconGlyph the glyph name.
194     */
195    public void setIconGlyph(String iconGlyph)
196    {
197        _iconGlyph = iconGlyph;
198    }
199    
200    /**
201     * Retrieves the CSS class to use for decorator above the main icon
202     * @return the glyph name.
203     */
204    public String getIconDecorator()
205    {
206        return _iconDecorator;
207    }
208    
209    /**
210     * Set the CSS class to use for decorator above the main icon
211     * @param iconDecorator the glyph name.
212     */
213    public void setIconDecorator(String iconDecorator)
214    {
215        _iconDecorator = iconDecorator;
216    }
217    
218    /**
219     * Retrieves the URL of the small icon without the context path.
220     * @return the icon URL for the small image 16x16.
221     */
222    public String getSmallIcon()
223    {
224        return _smallIcon;
225    }
226    
227    /**
228     * Set the URL of the small icon.
229     * @param smallIcon the URL of the small icon, without the context path.
230     */
231    public void setSmallIcon(String smallIcon)
232    {
233        _smallIcon = smallIcon;
234    }
235    
236    /**
237     * Retrieves the URL of the small icon without the context path.
238     * @return the icon URL for the medium sized image 32x32.
239     */
240    public String getMediumIcon()
241    {
242        return _mediumIcon;
243    }
244    
245    /**
246     * Set the URL of the medium icon.
247     * @param mediumIcon the URL of the medium icon, without the context path.
248     */
249    public void setMediumIcon(String mediumIcon)
250    {
251        _mediumIcon = mediumIcon;
252    }
253    
254    /**
255     * Retrieves the URL of the small icon without the context path.
256     * @return the icon URL for the large image 48x48.
257     */
258    public String getLargeIcon()
259    {
260        return _largeIcon;
261    }
262    
263    /**
264     * Set the URL of the large icon.
265     * @param largeIcon the URL of the large icon, without the context path.
266     */
267    public void setLargeIcon(String largeIcon)
268    {
269        _largeIcon = largeIcon;
270    }
271    
272    /**
273     * Determines if the view is for internal use only
274     * @return <code>true</code> if the view is for internal use only, <code>false</code> otherwise
275     */
276    public boolean isInternal()
277    {
278        return _isInternal;
279    }
280
281    /**
282     * Set the internal status
283     * @param isInternal <code>true</code> to make the view for internal use only, <code>false</code> otherwise
284     */
285    public void setInternal(boolean isInternal)
286    {
287        _isInternal = isInternal;
288    }
289    
290    public List<ViewItem> getViewItems()
291    {
292        return Collections.unmodifiableList(this._items);
293    }
294    
295    public void addViewItem(ViewItem item)
296    {
297        _items.add(item);
298    }
299    
300    public void insertViewItem(ViewItem item, int index)
301    {
302        if (index >= 0 && index <= _items.size())
303        {
304            _items.add(index, item);
305        }
306        else
307        {
308            throw new IllegalArgumentException("Unable to insert an item at index " + index + ". This group contains " + _items.size() + " items.");
309        }
310    }
311    
312    public boolean removeViewItem(ViewItem item)
313    {
314        return _items.remove(item);
315    }
316    
317    public void clear()
318    {
319        _items.clear();
320    }
321
322    /**
323     * Converts the view in a JSON map
324     * @param context the context of the definitions included in the view
325     * @return The view as a JSON map
326     * @throws ProcessingException If an error occurs when converting the view
327     */
328    public Map<String, Object> toJSON(DefinitionContext context) throws ProcessingException
329    {
330        Map<String, Object> result = new LinkedHashMap<>();
331        
332        result.put("name", getName());
333        result.put("internal", isInternal());
334        result.put("label", getLabel());
335        result.put("description", getDescription());
336        
337        result.put("icon-glyph", getIconGlyph());
338        result.put("icon-decorator", getIconDecorator());
339        result.put("small-icon-path", getSmallIcon());
340        result.put("medium-icon-path", getMediumIcon());
341        result.put("large-icon-path", getLargeIcon());
342    
343        result.put("elements", ViewHelper.viewItemsToJSON(getViewItems(), context));
344        
345        return result;
346    }
347
348    /**
349     * Generates SAX events for the view
350     * @param contentHandler the {@link ContentHandler} that will receive the SAX events 
351     * @param context the context of the definitions included in the view
352     * @throws SAXException if an error occurs during the SAX events generation
353     */
354    @SuppressWarnings("static-access")
355    public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
356    {
357        AttributesImpl attributes = new AttributesImpl();
358        attributes.addCDATAAttribute("name", getName());
359        attributes.addCDATAAttribute("internal", String.valueOf(isInternal()));
360        
361        XMLUtils.startElement(contentHandler, "view", attributes);
362        
363        XMLUtils.createI18nElementIfNotNull(contentHandler, "label", getLabel());
364        XMLUtils.createI18nElementIfNotNull(contentHandler, "description", getDescription());
365        
366        XMLUtils.startElement(contentHandler, "icons");
367        XMLUtils.createElementIfNotNull(contentHandler, "icon-glyph", getIconGlyph());
368        XMLUtils.createElementIfNotNull(contentHandler, "icon-decorator", getIconDecorator());
369        XMLUtils.createElementIfNotNull(contentHandler, "small-icon-path", getSmallIcon());
370        XMLUtils.createElementIfNotNull(contentHandler, "medium-icon-path", getMediumIcon());
371        XMLUtils.createElementIfNotNull(contentHandler, "large-icon-path", getLargeIcon());
372        XMLUtils.endElement(contentHandler, "icons");
373    
374        for (ViewItem viewItem : getViewItems())
375        {
376            viewItem.toSAX(contentHandler, context);
377        }
378
379        XMLUtils.endElement(contentHandler, "view");
380    }
381    
382    /**
383     * Include the given view to the current one.
384     * Add the items of the view to include if they are not already present in the current view
385     * @param viewToInclude the view to include
386     */
387    public void includeView(View viewToInclude)
388    {
389        View referenceView = new View();
390        referenceView.addViewItems(getViewItems());
391        ViewHelper.addViewAccessorItems(this, viewToInclude, referenceView, StringUtils.EMPTY);
392    }
393
394    @Override
395    public int hashCode()
396    {
397        return Objects.hash(_items, _name);
398    }
399    
400    @Override
401    public boolean equals(Object obj)
402    {
403        if (this == obj)
404        {
405            return true;
406        }
407        if (obj == null)
408        {
409            return false;
410        }
411        if (getClass() != obj.getClass())
412        {
413            return false;
414        }
415        View other = (View) obj;
416        return Objects.equals(_items, other._items) && Objects.equals(_name, other._name);
417    }
418
419    /**
420     * Indicates whether some other object is "equal to" this one.
421     * @param obj the reference object with which to compare.
422     * @param checkDetails <code>true</code> to check the view's details during comparison (label, description, icon, ...)
423     * @return <code>true</code> if this object is the same as the given obj, <code>false</code> otherwise.
424     */
425    public boolean equals(Object obj, boolean checkDetails)
426    {
427        if (!equals(obj))
428        {
429            return false;
430        }
431        else if (checkDetails)
432        {
433            View other = (View) obj;
434            
435            for (int i = 0; i < _items.size(); i++)
436            {
437                ViewItem item = _items.get(i);
438                ViewItem otherItem = other._items.get(i);
439                
440                if (!item.equals(otherItem, checkDetails))
441                {
442                    return false;
443                }
444            }
445            
446            return Objects.equals(_description, other._description) && Objects.equals(_iconDecorator, other._iconDecorator) && Objects.equals(_iconGlyph, other._iconGlyph)
447                    && _isInternal == other._isInternal && Objects.equals(_label, other._label);
448        }
449        else
450        {
451            return true;
452        }
453    }
454}