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.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.cocoon.xml.AttributesImpl;
028import org.xml.sax.ContentHandler;
029import org.xml.sax.SAXException;
030
031import org.ametys.core.util.XMLUtils;
032import org.ametys.runtime.model.type.DataContext;
033import org.ametys.runtime.model.type.ElementType;
034import org.ametys.runtime.model.type.ModelItemType;
035import org.ametys.runtime.model.type.ModelItemTypeConstants;
036
037/**
038 * Class for group of model items
039 */
040public class ModelItemGroup extends AbstractModelItem implements ModelItemContainer
041{
042    /** Id for model item group types */
043    public static final String DEFAULT_TYPE_ID = "composite";
044    
045    private List<ModelItem> _children = new ArrayList<>();
046    private ElementDefinition<Boolean> _switcher;
047    private ModelItemType _type;
048
049    /**
050     * Default constructor.
051     */
052    public ModelItemGroup()
053    {
054        super();
055    }
056    
057    /**
058     * Constructor used to create simple models and items 
059     * @param name the name of the definition
060     * @param children the group's children
061     */
062    public ModelItemGroup(String name, ModelItem... children)
063    {
064        super(name);
065        Arrays.stream(children)
066            .forEach(item -> addChild(item));
067    }
068    
069    public Collection<ModelItem> getModelItems()
070    {
071        return getChildren();
072    }
073    
074    /**
075     * Retrieves the list of children model items
076     * @return the children
077     */
078    public List<ModelItem> getChildren()
079    {
080        return getChildren(true);
081    }
082    
083    /**
084     * Retrieves the list of children model items, with or without the switcher
085     * @param withSwitch true to retrieve the switcher with the other children
086     * @return the children with or without the switcher
087     */
088    public List<ModelItem> getChildren(boolean withSwitch)
089    {
090        if (withSwitch || _switcher == null)
091        {
092            return Collections.unmodifiableList(_children);
093        }
094        
095        List<ModelItem> childrenWithoutSwitcher = new ArrayList<>(_children);
096        childrenWithoutSwitcher.remove(_switcher);
097        return childrenWithoutSwitcher;
098    }
099    
100    /**
101     * Add a child in the group
102     * @param child the item to add
103     */
104    public void addChild(ModelItem child)
105    {
106        addChild(child, false);
107    }
108    
109    /**
110     * Add a child in the group
111     * @param child the child to add
112     * @param isGroupSwitch true if the child to add is the group switch, false otherwise
113     */
114    @SuppressWarnings("unchecked")
115    public void addChild(ModelItem child, boolean isGroupSwitch)
116    {
117        _children.add(child);
118        child.setParent(this);
119        
120        if (isGroupSwitch)
121        {
122            if (_switcher == null)
123            {
124                if (child instanceof ElementDefinition)
125                {
126                    ElementType type = ((ElementDefinition) child).getType();
127                    if (ModelItemTypeConstants.BOOLEAN_TYPE_ID.equals(type.getId()))
128                    {
129                        _switcher = (ElementDefinition<Boolean>) child;
130                    }
131                }
132                
133                if (_switcher == null)
134                {
135                    // If switcher is still null, the type of child is not compatible
136                    throw new RuntimeException("The group '" + getLabel() + "' has a switch '" + child + "' that is not valid because it is not a boolean.");
137                }
138            }
139            else
140            {
141                throw new RuntimeException("At least two group-switches have been defined for the configuration group '" + getLabel() + "'. These parameters are '" + _switcher + "' and '" + child + "'.");
142            }
143        }
144    }
145    
146    /**
147     * Retrieves the switcher element definition
148     * @return the switcher
149     */
150    public ElementDefinition<Boolean> getSwitcher()
151    {
152        return _switcher;
153    }
154    
155    @Override
156    public void setModel(Model model)
157    {
158        super.setModel(model);
159        for (ModelItem modelItem : _children)
160        {
161            modelItem.setModel(model);
162        }
163    }
164    
165    @Override
166    public ModelItemType getType()
167    {
168        return _type;
169    }
170
171    public void setType(ModelItemType type)
172    {
173        _type = type;
174    }
175    
176    @Override
177    protected Map<String, Object> _toJSON(DefinitionContext context)
178    {
179        return _toJSON(context, true);
180    }
181    
182    /**
183     * Converts the model item group in a JSON map
184     * @param context the context of the definition
185     * @param includeChildren true to iterate and add children as elements in the returned JSON map, false otherwise
186     * @return The model item as a JSON map
187     */
188    public Map<String, Object> toJSON(DefinitionContext context, boolean includeChildren)
189    {
190        if (_shouldJSONBeEmpty(context))
191        {
192            return Map.of();
193        }
194        else
195        {
196            return _toJSON(context, includeChildren);
197        }
198    }
199    
200    /**
201     * Converts the model item group in a JSON map
202     * @param context the context of the definition
203     * @param includeChildren true to iterate and add children as elements in the returned JSON map, false otherwise
204     * @return The model item as a JSON map
205     */
206    protected Map<String, Object> _toJSON(DefinitionContext context, boolean includeChildren)
207    {
208        Map<String, Object> result = super._toJSON(context);
209        
210        ModelItemType type = getType();
211        result.put("type", type != null ? type.getId() : DEFAULT_TYPE_ID);
212        
213        ElementDefinition<Boolean> switcher = getSwitcher();
214        if (switcher != null)
215        {
216            Map<String, Object> switcherToJSON = new HashMap<>();
217            
218            switcherToJSON.put("id", switcher.getName());
219            switcherToJSON.put("label", switcher.getLabel());
220            
221            if (switcher.getType() != null)
222            {
223                switcherToJSON.put("default-value", switcher.getType().valueToJSONForClient(switcher.getDefaultValue(), DataContext.newInstance()));
224            }
225            
226            result.put("switcher", switcherToJSON);
227        }
228        
229        // Do not include switcher in children because it is already included above
230        List<ModelItem> children = getChildren(false);
231        if (includeChildren && !children.isEmpty())
232        {
233            Map<String, Object> elements = new LinkedHashMap<>();
234            for (ModelItem child : children)
235            {
236                String name = child.getName();
237                if (name != null)
238                {
239                    elements.put(name, child.toJSON(context));
240                }
241            }
242            
243            result.put("elements", elements);
244        }
245        
246        return result;
247    }
248    
249    /**
250     * {@inheritDoc}
251     * Do not generate SAX events for children.
252     * This method has been created to generate SAX events for views, the children are taken into account via view items children 
253     */
254    @SuppressWarnings("static-access")
255    @Override
256    public void toSAX(ContentHandler contentHandler, DefinitionContext context) throws SAXException
257    {
258        super.toSAX(contentHandler, context);
259        
260        ElementDefinition<Boolean> switcher = getSwitcher();
261        if (switcher != null)
262        {
263            AttributesImpl switcherAttributes = new AttributesImpl();
264            switcherAttributes.addCDATAAttribute("name", switcher.getName());
265            
266            XMLUtils.startElement(contentHandler, "switcher", switcherAttributes);
267            XMLUtils.createI18nElementIfNotNull(contentHandler, "label", switcher.getLabel());
268            
269            if (switcher.getType() != null)
270            {
271                switcher.getType().valueToSAX(contentHandler, "default-value", switcher.getDefaultValue(), DataContext.newInstance());
272            }
273            
274            XMLUtils.endElement(contentHandler, "switcher");
275        }
276    }
277    
278    /**
279     * Creates a {@link ModelItemGroup}
280     * @param name the group's name
281     * @param children the group's children
282     * @return the created {@link ModelItemGroup}
283     */
284    public static ModelItemGroup of(String name, ModelItem... children)
285    {
286        return new ModelItemGroup(name, children);
287    }
288}