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        item.setParent(this);
299    }
300    
301    public void insertViewItem(ViewItem item, int index)
302    {
303        if (index >= 0 && index <= _items.size())
304        {
305            _items.add(index, item);
306        }
307        else
308        {
309            throw new IllegalArgumentException("Unable to insert an item at index " + index + ". This group contains " + _items.size() + " items.");
310        }
311    }
312    
313    public boolean removeViewItem(ViewItem item)
314    {
315        return _items.remove(item);
316    }
317    
318    public void clear()
319    {
320        _items.clear();
321    }
322
323    /**
324     * Converts the view in a JSON map
325     * @param context the context of the definitions included in the view
326     * @return The view as a JSON map
327     * @throws ProcessingException If an error occurs when converting the view
328     */
329    public Map<String, Object> toJSON(DefinitionContext context) throws ProcessingException
330    {
331        Map<String, Object> result = new LinkedHashMap<>();
332        
333        result.put("name", getName());
334        result.put("internal", isInternal());
335        result.put("label", getLabel());
336        result.put("description", getDescription());
337        
338        result.put("icon-glyph", getIconGlyph());
339        result.put("icon-decorator", getIconDecorator());
340        result.put("small-icon-path", getSmallIcon());
341        result.put("medium-icon-path", getMediumIcon());
342        result.put("large-icon-path", getLargeIcon());
343    
344        result.put("elements", ViewHelper.viewItemsToJSON(getViewItems(), context));
345        
346        return result;
347    }
348
349    /**
350     * Generates SAX events for the view
351     * @param contentHandler the {@link ContentHandler} that will receive the SAX events 
352     * @param context the context of the definitions included in the view
353     * @throws SAXException if an error occurs during the SAX events generation
354     */
355    @SuppressWarnings("static-access")
356    public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
357    {
358        AttributesImpl attributes = new AttributesImpl();
359        attributes.addCDATAAttribute("name", getName());
360        attributes.addCDATAAttribute("internal", String.valueOf(isInternal()));
361        
362        XMLUtils.startElement(contentHandler, "view", attributes);
363        
364        XMLUtils.createI18nElementIfNotNull(contentHandler, "label", getLabel());
365        XMLUtils.createI18nElementIfNotNull(contentHandler, "description", getDescription());
366        
367        XMLUtils.startElement(contentHandler, "icons");
368        XMLUtils.createElementIfNotNull(contentHandler, "icon-glyph", getIconGlyph());
369        XMLUtils.createElementIfNotNull(contentHandler, "icon-decorator", getIconDecorator());
370        XMLUtils.createElementIfNotNull(contentHandler, "small-icon-path", getSmallIcon());
371        XMLUtils.createElementIfNotNull(contentHandler, "medium-icon-path", getMediumIcon());
372        XMLUtils.createElementIfNotNull(contentHandler, "large-icon-path", getLargeIcon());
373        XMLUtils.endElement(contentHandler, "icons");
374    
375        for (ViewItem viewItem : getViewItems())
376        {
377            viewItem.toSAX(contentHandler, context);
378        }
379
380        XMLUtils.endElement(contentHandler, "view");
381    }
382    
383    /**
384     * Include the given view to the current one.
385     * Add the items of the view to include if they are not already present in the current view
386     * @param viewToInclude the view to include
387     */
388    public void includeView(View viewToInclude)
389    {
390        View referenceView = new View();
391        referenceView.addViewItems(getViewItems());
392        ViewHelper.addViewAccessorItems(this, viewToInclude, referenceView, StringUtils.EMPTY);
393    }
394
395    @Override
396    public int hashCode()
397    {
398        return Objects.hash(_items, _name);
399    }
400    
401    @Override
402    public boolean equals(Object obj)
403    {
404        if (this == obj)
405        {
406            return true;
407        }
408        if (obj == null)
409        {
410            return false;
411        }
412        if (getClass() != obj.getClass())
413        {
414            return false;
415        }
416        View other = (View) obj;
417        return Objects.equals(_items, other._items) && Objects.equals(_name, other._name);
418    }
419
420    /**
421     * Indicates whether some other object is "equal to" this one.
422     * @param obj the reference object with which to compare.
423     * @param checkDetails <code>true</code> to check the view's details during comparison (label, description, icon, ...)
424     * @return <code>true</code> if this object is the same as the given obj, <code>false</code> otherwise.
425     */
426    public boolean equals(Object obj, boolean checkDetails)
427    {
428        if (!equals(obj))
429        {
430            return false;
431        }
432        else if (checkDetails)
433        {
434            View other = (View) obj;
435            
436            for (int i = 0; i < _items.size(); i++)
437            {
438                ViewItem item = _items.get(i);
439                ViewItem otherItem = other._items.get(i);
440                
441                if (!item.equals(otherItem, checkDetails))
442                {
443                    return false;
444                }
445            }
446            
447            return Objects.equals(_description, other._description) && Objects.equals(_iconDecorator, other._iconDecorator) && Objects.equals(_iconGlyph, other._iconGlyph)
448                    && _isInternal == other._isInternal && Objects.equals(_label, other._label);
449        }
450        else
451        {
452            return true;
453        }
454    }
455}