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.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Objects;
023
024import org.apache.cocoon.xml.AttributesImpl;
025import org.apache.commons.lang3.StringUtils;
026import org.xml.sax.ContentHandler;
027import org.xml.sax.SAXException;
028
029import org.ametys.core.util.SizeUtils.ExcludeFromSizeCalculation;
030import org.ametys.core.util.XMLUtils;
031import org.ametys.runtime.i18n.I18nizableText;
032import org.ametys.runtime.model.exception.BadItemTypeException;
033
034/**
035 * View reference to a group of model items
036 * @param <T> type of the referenced model item group
037 */
038public class ModelViewItemGroup<T extends ModelItemGroup> extends AbstractViewItemGroup implements ModelViewItem<T>
039{
040    @ExcludeFromSizeCalculation
041    private T _definition;
042    
043    @ExcludeFromSizeCalculation
044    private ViewItemAccessor _parent;
045    
046    /**
047     * Creates a {@link ModelViewItemGroup} with the items of the given {@link ModelItemGroup}
048     * @param modelItem the model item group
049     * @return the created {@link ModelViewItemGroup}
050     * @throws IllegalArgumentException if the model item is <code>null</code>
051     */
052    public static ModelViewItemGroup of(ModelItemGroup modelItem) throws IllegalArgumentException
053    {
054        if (modelItem == null)
055        {
056            throw new IllegalArgumentException("Unable to create the view from a null model");
057        }
058        else
059        {
060            return ViewHelper.createViewItemAccessor(List.of(modelItem));
061        }
062    }
063    
064    /**
065     * Creates a {@link ModelViewItemGroup} with the given items
066     * @param modelItem the model item containing items definitions
067     * @param itemPaths the paths of the items to put in the view item
068     * @return the created {@link ModelViewItemGroup}
069     * @throws IllegalArgumentException if the model item is <code>null</code> or if an item path is <code>null</code>, empty, or is not defined in the given model items
070     * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item
071     */
072    public static ModelViewItemGroup of(ModelItemGroup modelItem, String... itemPaths) throws IllegalArgumentException, BadItemTypeException
073    {
074        if (modelItem == null)
075        {
076            throw new IllegalArgumentException("Unable to create the view from a null model");
077        }
078        else
079        {
080            return ViewHelper.createViewItemAccessor(List.of(modelItem), itemPaths);
081        }
082    }
083    
084    public T getDefinition()
085    {
086        return _definition;
087    }
088    
089    public void setDefinition(T definition)
090    {
091        if (definition == null)
092        {
093            throw new IllegalArgumentException("Try to set a null definition to the model view item group");
094        }
095        
096        _definition = definition;
097    }
098    
099    public ViewItemAccessor getParent()
100    {
101        return _parent;
102    }
103    
104    public void setParent(ViewItemAccessor parent)
105    {
106        _parent = parent;
107    }
108    
109    public String getName()
110    {
111        if (_definition != null)
112        {
113            return _definition.getName();
114        }
115        else
116        {
117            return null;
118        }
119    }
120    
121    @Override
122    public I18nizableText getLabel()
123    {
124        I18nizableText label = super.getLabel();
125        if (label != null)
126        {
127            return label;
128        }
129        
130        return getDefinition().getLabel();
131    }
132    
133    @Override
134    public I18nizableText getDescription()
135    {
136        I18nizableText desc = super.getDescription();
137        if (desc != null)
138        {
139            return desc;
140        }
141        
142        return getDefinition().getDescription();
143    }
144    
145    public Map<String, Object> toJSON(DefinitionContext context)
146    {
147        ModelItemGroup definition = getDefinition();
148        if (definition != null)
149        {
150            Map<String, Object> result = definition.toJSON(context, false);
151            if (!result.isEmpty())
152            {
153                // use overridden label and description if present  
154                result.put("label", getLabel());
155                result.put("description", getDescription());
156                result.put("role", getRole());
157                
158                if (StringUtils.isEmpty(getName()))
159                {
160                    result.put("unnamed-group", true);
161                }
162                
163                result.putAll(_childrenToJSON(context));
164            }
165            return result;
166        }
167        
168        return Map.of();
169    }
170    
171    /**
172     * 
173     * Converts the group's children in a JSON map
174     * @param context the context of the definitions referenced in this group and/or the children
175     * @return The children as a JSON map
176     */
177    protected Map<String, Object> _childrenToJSON(DefinitionContext context)
178    {
179        Map<String, Object> result = new HashMap<>();
180        
181        List<ViewItem> viewItems = _getChildrenWithoutSwitcher();
182        if (!viewItems.isEmpty())
183        {
184            result.put("elements", ViewHelper.viewItemsToJSON(viewItems, context));
185        }
186        
187        return result;
188    }
189    
190    @SuppressWarnings("static-access")
191    public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
192    {
193        ModelItemGroup definition = getDefinition();
194        if (definition != null)
195        {
196            String tagName = context.getItemTagName().orElse(DEFAULT_ITEM_TAG_NAME);
197            
198            AttributesImpl attributes = new AttributesImpl();
199            attributes.addCDATAAttribute("name", definition.getName());
200            attributes.addCDATAAttribute("plugin", definition.getPluginName());
201            attributes.addCDATAAttribute("path", definition.getPath());
202            
203            String typeId = definition.getType() != null ? definition.getType().getId() : ModelItemGroup.DEFAULT_TYPE_ID;
204            attributes.addCDATAAttribute("type", typeId);
205            
206            XMLUtils.startElement(contentHandler, tagName, attributes);
207            
208            XMLUtils.createElementIfNotNull(contentHandler, "role", getRole());
209            XMLUtils.createI18nElementIfNotNull(contentHandler, "label", getLabel());
210            XMLUtils.createI18nElementIfNotNull(contentHandler, "description", getDescription());
211            
212            definition.toSAX(contentHandler, context);
213            
214            for (ViewItem viewItem : _getChildrenWithoutSwitcher())
215            {
216                viewItem.toSAX(contentHandler, context);
217            }
218            
219            XMLUtils.endElement(contentHandler, tagName);
220        }
221    }
222    
223    private List<ViewItem> _getChildrenWithoutSwitcher()
224    {
225        ElementDefinition switcher = getDefinition().getSwitcher();
226        if (switcher == null)
227        {
228            return getViewItems();
229        }
230        
231        List<ViewItem> childrenWithoutSwitcher = new ArrayList<>();
232        for (ViewItem child : getViewItems())
233        {
234            if (child instanceof ViewElement)
235            {
236                ElementDefinition childDefinitonReference = ((ViewElement) child).getDefinition();
237                if (!switcher.equals(childDefinitonReference))
238                {
239                    childrenWithoutSwitcher.add(child);
240                }
241            }
242        }
243        
244        return childrenWithoutSwitcher;
245    }
246    
247    @SuppressWarnings("unchecked")
248    @Override
249    public void copyTo(ViewItem item)
250    {
251        super.copyTo(item);
252        
253        assert item instanceof ModelViewItemGroup;
254        ((ModelViewItemGroup) item).setDefinition(getDefinition());
255    }
256    
257    public ModelViewItemGroup createInstance()
258    {
259        return new ModelViewItemGroup();
260    }
261
262    @Override
263    public int hashCode()
264    {
265        final int prime = 31;
266        int result = super.hashCode();
267        result = prime * result + Objects.hash(_definition);
268        return result;
269    }
270
271    @Override
272    public boolean equals(Object obj)
273    {
274        if (this == obj)
275        {
276            return true;
277        }
278        if (!super.equals(obj))
279        {
280            return false;
281        }
282        if (getClass() != obj.getClass())
283        {
284            return false;
285        }
286        ModelViewItemGroup other = (ModelViewItemGroup) obj;
287        return Objects.equals(_definition, other._definition);
288    }
289    
290    @Override
291    public String toString()
292    {
293        return _definition.toString() + ": " + _children.toString();
294    }
295}