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