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