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    private DisabledItemRendering _disabledItemRendering = DisabledItemRendering.DEFAULT;
047    
048    /**
049     * Creates a {@link ModelViewItemGroup} with the items of the given {@link ModelItemGroup}
050     * @param modelItem the model item group
051     * @return the created {@link ModelViewItemGroup}
052     * @throws IllegalArgumentException if the model item is <code>null</code>
053     */
054    public static ModelViewItemGroup of(ModelItemGroup modelItem) throws IllegalArgumentException
055    {
056        if (modelItem == null)
057        {
058            throw new IllegalArgumentException("Unable to create the view from a null model");
059        }
060        else
061        {
062            return ViewHelper.createViewItemAccessor(List.of(modelItem));
063        }
064    }
065    
066    /**
067     * Creates a {@link ModelViewItemGroup} with the given items
068     * @param modelItem the model item containing items definitions
069     * @param itemPaths the paths of the items to put in the view item
070     * @return the created {@link ModelViewItemGroup}
071     * @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
072     * @throws BadItemTypeException if a segment in a path (but not the last) does not represent a group item
073     */
074    public static ModelViewItemGroup of(ModelItemGroup modelItem, String... itemPaths) throws IllegalArgumentException, BadItemTypeException
075    {
076        if (modelItem == null)
077        {
078            throw new IllegalArgumentException("Unable to create the view from a null model");
079        }
080        else
081        {
082            return ViewHelper.createViewItemAccessor(List.of(modelItem), itemPaths);
083        }
084    }
085    
086    public T getDefinition()
087    {
088        return _definition;
089    }
090    
091    public void setDefinition(T definition)
092    {
093        if (definition == null)
094        {
095            throw new IllegalArgumentException("Try to set a null definition to the model view item group");
096        }
097        
098        _definition = definition;
099    }
100    
101    public ViewItemAccessor getParent()
102    {
103        return _parent;
104    }
105    
106    public void setParent(ViewItemAccessor parent)
107    {
108        _parent = parent;
109    }
110    
111    @Override
112    public I18nizableText getLabel()
113    {
114        I18nizableText label = super.getLabel();
115        if (label != null)
116        {
117            return label;
118        }
119        
120        return getDefinition().getLabel();
121    }
122    
123    @Override
124    public I18nizableText getDescription()
125    {
126        I18nizableText desc = super.getDescription();
127        if (desc != null)
128        {
129            return desc;
130        }
131        
132        return getDefinition().getDescription();
133    }
134    
135    public DisabledItemRendering getDisabledItemRendering()
136    {
137        return _disabledItemRendering;
138    }
139    
140    public void setDisabledItemRendering(DisabledItemRendering disabledItemRendering)
141    {
142        _disabledItemRendering = disabledItemRendering;
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                DisabledItemRendering disabledItemRendering = getDisabledItemRendering();
159                if (disabledItemRendering != DisabledItemRendering.DEFAULT)
160                {
161                    result.put("disabledItemRendering", disabledItemRendering.name().toLowerCase());
162                }
163                
164                if (StringUtils.isEmpty(getName()))
165                {
166                    result.put("unnamed-group", true);
167                }
168                
169                result.putAll(_childrenToJSON(context));
170            }
171            return result;
172        }
173        
174        return Map.of();
175    }
176    
177    /**
178     * 
179     * Converts the group's children in a JSON map
180     * @param context the context of the definitions referenced in this group and/or the children
181     * @return The children as a JSON map
182     */
183    protected Map<String, Object> _childrenToJSON(DefinitionContext context)
184    {
185        Map<String, Object> result = new HashMap<>();
186        
187        List<ViewItem> viewItems = _getChildrenWithoutSwitcher();
188        if (!viewItems.isEmpty())
189        {
190            result.put("elements", ViewHelper.viewItemsToJSON(viewItems, context));
191        }
192        
193        return result;
194    }
195    
196    @SuppressWarnings("static-access")
197    public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
198    {
199        ModelItemGroup definition = getDefinition();
200        if (definition != null)
201        {
202            String tagName = context.getItemTagName().orElse(DEFAULT_ITEM_TAG_NAME);
203            
204            AttributesImpl attributes = new AttributesImpl();
205            attributes.addCDATAAttribute("name", definition.getName());
206            attributes.addCDATAAttribute("plugin", definition.getPluginName());
207            attributes.addCDATAAttribute("path", definition.getPath());
208            
209            String typeId = definition.getType() != null ? definition.getType().getId() : ModelItemGroup.DEFAULT_TYPE_ID;
210            attributes.addCDATAAttribute("type", typeId);
211            
212            DisabledItemRendering disabledItemRendering = getDisabledItemRendering();
213            if (disabledItemRendering != DisabledItemRendering.DEFAULT)
214            {
215                attributes.addCDATAAttribute("disabled", disabledItemRendering.name().toLowerCase());
216            }
217            
218            XMLUtils.startElement(contentHandler, tagName, attributes);
219            
220            XMLUtils.createElementIfNotNull(contentHandler, "role", getRole());
221            XMLUtils.createI18nElementIfNotNull(contentHandler, "label", getLabel());
222            XMLUtils.createI18nElementIfNotNull(contentHandler, "description", getDescription());
223            
224            definition.toSAX(contentHandler, context);
225            
226            additionalDataToSAX(contentHandler, context);
227            
228            for (ViewItem viewItem : _getChildrenWithoutSwitcher())
229            {
230                viewItem.toSAX(contentHandler, context);
231            }
232            
233            XMLUtils.endElement(contentHandler, tagName);
234        }
235    }
236    
237    /**
238     * Permit to generate SAX events for additional data in sub classes
239     * @param contentHandler the {@link ContentHandler} that will receive the SAX events
240     * @param context the context of the definitions included in the view item
241     * @throws SAXException if an error occurs during the SAX events generation
242     */
243    protected void additionalDataToSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
244    {
245        // Do nothing
246    }
247    
248    private List<ViewItem> _getChildrenWithoutSwitcher()
249    {
250        ElementDefinition switcher = getDefinition().getSwitcher();
251        if (switcher == null)
252        {
253            return getViewItems();
254        }
255        
256        List<ViewItem> childrenWithoutSwitcher = new ArrayList<>();
257        for (ViewItem child : getViewItems())
258        {
259            if (child instanceof ViewElement)
260            {
261                ElementDefinition childDefinitonReference = ((ViewElement) child).getDefinition();
262                if (!switcher.equals(childDefinitonReference))
263                {
264                    childrenWithoutSwitcher.add(child);
265                }
266            }
267        }
268        
269        return childrenWithoutSwitcher;
270    }
271    
272    @SuppressWarnings("unchecked")
273    @Override
274    public void copyTo(ViewItem item)
275    {
276        super.copyTo(item);
277        
278        assert item instanceof ModelViewItemGroup;
279        ModelViewItemGroup group = (ModelViewItemGroup) item;
280        
281        group.setDefinition(getDefinition());
282        group.setDisabledItemRendering(getDisabledItemRendering());
283    }
284    
285    public ModelViewItemGroup createInstance()
286    {
287        return new ModelViewItemGroup();
288    }
289
290    @Override
291    public int hashCode()
292    {
293        final int prime = 31;
294        int result = super.hashCode();
295        result = prime * result + Objects.hash(_definition);
296        return result;
297    }
298
299    @Override
300    public boolean equals(Object obj)
301    {
302        if (this == obj)
303        {
304            return true;
305        }
306        if (!super.equals(obj))
307        {
308            return false;
309        }
310        if (getClass() != obj.getClass())
311        {
312            return false;
313        }
314        ModelViewItemGroup other = (ModelViewItemGroup) obj;
315        return Objects.equals(_definition, other._definition);
316    }
317    
318    @Override
319    public String toString()
320    {
321        return _definition.toString() + ": " + _children.toString();
322    }
323}