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.Comparator;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import org.slf4j.Logger;
027
028import org.ametys.runtime.i18n.I18nizableText;
029
030/**
031 * Helper for {@link CategorizedElementDefinitionWrapper}
032 */
033public final class CategorizedElementDefinitionHelper
034{
035    private CategorizedElementDefinitionHelper()
036    {
037        //Nothing
038    }
039    
040    /**
041     * Generate the view for categorized element definition wrappers, using comparators to sort the categories, groups and elements
042     * @param <T> type of the wrappers
043     * @param wrappers the wrappers
044     * @param categoriesComparator {@link Comparator} used for the categories (can be null to keep order)
045     * @param groupsComparator {@link Comparator} for the groups (can be null to keep order)
046     * @param elementsComparator {@link Comparator} for the elements (can be null to keep order)
047     * @return A {@link View} ordered from the {@link ModelItem} using the {@link Comparator}
048     */
049    public static <T extends CategorizedElementDefinitionWrapper> View buildViewFromCategories(Collection<T> wrappers, Comparator<I18nizableText> categoriesComparator, Comparator<I18nizableText> groupsComparator, Comparator<T> elementsComparator)
050    {
051        View view = new View();
052        
053        Map<I18nizableText, Map<I18nizableText, List<T>>> categories = categorizeElementDefinitionWrappers(wrappers);
054        
055        Collection<I18nizableText> sortedCategories = _sort(categories.keySet(), categoriesComparator);
056        for (I18nizableText categoryLabel : sortedCategories)
057        {
058            ViewItem categoryViewItem = _buildCategoryViewItem(categoryLabel, categories.get(categoryLabel), groupsComparator, elementsComparator);
059            view.addViewItem(categoryViewItem);
060        }
061        return view;
062    }
063    
064    /**
065     * Organize a collection of categorized element definition wrappers by categories and groups.
066     * @param <T> type of the wrappers
067     * @param wrappers a collection of element definition wrappers.
068     * @return a Map with the same element definition wrappers, sorted first by category then by group.
069     */
070    public static <T extends CategorizedElementDefinitionWrapper> Map<I18nizableText, Map<I18nizableText, List<T>>> categorizeElementDefinitionWrappers(Collection<T> wrappers)
071    {
072        Map<I18nizableText, Map<I18nizableText, List<T>>> categories = new HashMap<>();
073
074        // Classify parameters by groups and categories
075        for (T parameter : wrappers)
076        {
077            I18nizableText displayCategory = parameter.getDisplayCategory();
078            I18nizableText displayGroup = parameter.getDisplayGroup();
079
080            // Get the map of groups of the category
081            Map<I18nizableText, List<T>> category = categories.get(displayCategory);
082            if (category == null)
083            {
084                category = new HashMap<>();
085                categories.put(displayCategory, category);
086            }
087
088            // Get the map of parameters of the group
089            List<T> group = category.get(displayGroup);
090            if (group == null)
091            {
092                group = new ArrayList<>();
093                category.put(displayGroup, group);
094            }
095
096            group.add(parameter);
097        }
098        
099        return categories;
100    }
101    
102    private static <T extends CategorizedElementDefinitionWrapper> ViewItem _buildCategoryViewItem(I18nizableText categoryLabel, Map<I18nizableText, List<T>> groups, Comparator<I18nizableText> groupsComparator, Comparator<T> elementsComparator) throws IllegalArgumentException
103    {
104        SimpleViewItemGroup categoryViewItem = new SimpleViewItemGroup();
105        categoryViewItem.setRole(ViewItemGroup.TAB_ROLE);
106        categoryViewItem.setLabel(categoryLabel);
107
108        Collection<I18nizableText> sortedGroups = _sort(groups.keySet(), groupsComparator);
109        for (I18nizableText groupLabel : sortedGroups)
110        {
111            ViewItem groupViewItem = _buildGroupViewItem(groupLabel, groups.get(groupLabel), elementsComparator);
112            categoryViewItem.addViewItem(groupViewItem);
113        }
114        return categoryViewItem;
115    }
116    
117    private static <T extends CategorizedElementDefinitionWrapper> ViewItem _buildGroupViewItem(I18nizableText groupLabel, List<T> wrappers, Comparator<T> elementsComparator) throws IllegalArgumentException
118    {
119        SimpleViewItemGroup groupViewItem = new SimpleViewItemGroup();
120        groupViewItem.setRole(ViewItemGroup.FIELDSET_ROLE);
121        groupViewItem.setLabel(groupLabel);
122
123        Collection<T> items = _sort(wrappers, elementsComparator);
124        for (T item : items)
125        {
126            ViewElement parameterViewItem = new ViewElement();
127            parameterViewItem.setDefinition(item.getDefinition());
128            groupViewItem.addViewItem(parameterViewItem);
129        }
130        
131        return groupViewItem;
132    }
133    
134    /**
135     * Sort a model item list using comparator
136     * @param <T> The type of item to compare
137     * @param items list of items to sort
138     * @param comparator a comparator, can be null to avoid sort
139     * @return a sorted list (or a copy of the list if the comparator is null)
140     */
141    private static <T> Collection<T> _sort(Collection<T> items, Comparator<T> comparator)
142    {
143        if (comparator == null)
144        {
145            return items;
146        }
147        else
148        {
149            List<T> sorted = new ArrayList<>(items);
150            sorted.sort(comparator);
151            return sorted;
152        }
153    }
154    
155    /**
156     * Validate parameters before writing
157     * @param definitionAndValues a map of all parameters and their values
158     * @param flatDefinitions flat definition of {@link ElementDefinition} from the model file
159     * @param logger a logger
160     * @return a map containing the potential errors
161     * @deprecated TODO NEWATTRIBUTEAPI_CONFIG RUNTIME-2851 remove this method when it is not necessary anymore to have the flat definitions
162     */
163    @Deprecated
164    public static Map<String, List<I18nizableText>> validateValuesForWriting (Map<String, DefinitionAndValue> definitionAndValues, Map<String, ElementDefinition> flatDefinitions, Logger logger)
165    {
166        Map<String, List<I18nizableText>> errorFields = new HashMap<>();
167        
168        for (ElementDefinition definition : flatDefinitions.values())
169        {
170            boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(definition, DefinitionAndValue.extractValues(definitionAndValues));
171            boolean isDisabled = ModelHelper.evaluateDisableConditions(definition.getDisableConditions(), definitionAndValues, logger);
172            
173            if (isGroupSwitchOn && !isDisabled)
174            {
175                Object value = null;
176                DefinitionAndValue definitionAndValue = definitionAndValues.get(definition.getName());
177                if (definitionAndValue != null)
178                {
179                    value = definitionAndValue.getValue();
180                }
181                
182                List<I18nizableText> errors = ModelHelper.validateValue(definition, value);
183                
184                if (!errors.isEmpty())
185                {
186                    logger.warn("The configuration parameter '{}' is not valid", definition.getName());
187                    errorFields.put(definition.getName(), errors);
188                }
189            }
190        }
191        
192        if (!errorFields.isEmpty())
193        {
194            logger.debug("Failed to save configuration because of invalid parameter values");
195        }
196        return errorFields;
197    }
198    
199    /**
200     * Validate parameters before writing
201     * @param definitionAndValues a map of all parameters and their values
202     * @param logger a logger
203     * @return a map containing the potential errors
204     */
205    public static Map<String, List<I18nizableText>> validateValuesForWriting (Map<String, DefinitionAndValue> definitionAndValues, Logger logger)
206    {
207        Map<String, List<I18nizableText>> errorFields = new HashMap<>();
208        for (Entry<String, DefinitionAndValue> entry : definitionAndValues.entrySet())
209        {
210            DefinitionAndValue definitionAndValue = entry.getValue();
211            ModelItem definition = definitionAndValue.getDefinition();
212            if (definition instanceof ElementDefinition)
213            {
214                ElementDefinition elementDefinition = (ElementDefinition) definition;
215                boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(elementDefinition, DefinitionAndValue.extractValues(definitionAndValues));
216                boolean isDisabled = ModelHelper.evaluateDisableConditions(elementDefinition.getDisableConditions(), definitionAndValues, logger);
217                
218                if (isGroupSwitchOn && !isDisabled)
219                {
220                    Object value = definitionAndValue.getValue();
221                    
222                    List<I18nizableText> errors = ModelHelper.validateValue(elementDefinition, value);
223                    
224                    if (!errors.isEmpty())
225                    {
226                        logger.warn("The configuration parameter '{}' is not valid", elementDefinition.getName());
227                        errorFields.put(elementDefinition.getName(), errors);
228                    }
229                }
230            }
231        }
232        
233        if (!errorFields.isEmpty())
234        {
235            logger.debug("Failed to save configuration because of invalid parameter values");
236        }
237        return errorFields;
238    }
239    
240    /**
241     * Compares the two given wrappers' position.
242     * Returns a negative integer, zero, or a positive integer as the position of the first wrapper
243     * is less than, equal to, or greater than the second.
244     * @param <T> type of the wrappers to compare
245     * @param wrapper1 the first wrapper
246     * @param wrapper2 the second wrapper
247     * @return a negative integer, zero, or a positive integer as the position of the first wrapper
248     * is less than, equal to, or greater than the second.
249     */
250    public static <T extends CategorizedElementDefinitionWrapper> int compareWrapperPositions(T wrapper1, T wrapper2)
251    {
252        int positionComparison = ((Long) wrapper1.getPosition()).compareTo(wrapper2.getPosition());
253        if (wrapper1.getPosition() < 0 && wrapper2.getPosition() < 0)
254        {
255            positionComparison = 0;
256        }
257        else if (wrapper1.getPosition() < 0 && wrapper2.getPosition() >= 0)
258        {
259            positionComparison = 1;
260        }
261        else if (wrapper2.getPosition() < 0)
262        {
263            positionComparison = -1;
264        }
265        
266        return positionComparison;
267    }
268}