001/*
002 *  Copyright 2024 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.disableconditions;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Optional;
021
022import org.apache.avalon.framework.component.Component;
023
024import org.ametys.runtime.model.ModelHelper;
025import org.ametys.runtime.model.ModelItem;
026import org.ametys.runtime.model.exception.BadItemTypeException;
027import org.ametys.runtime.model.exception.UndefinedItemPathException;
028import org.ametys.runtime.plugin.component.AbstractLogEnabled;
029
030/**
031 * Abstract evaluator for {@link DisableConditions}
032 */
033public class DefaultDisableConditionsEvaluator extends AbstractLogEnabled implements Component, DisableConditionsEvaluator
034{
035    /** The Avalon role */
036    public static final String ROLE = DefaultDisableConditionsEvaluator.class.getName();
037    
038    public boolean evaluateDisableConditions(ModelItem definition, String dataPath, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
039    {
040        return evaluateDisableConditions(definition, definition.getDisableConditions(), dataPath, Optional.empty(), values, Optional.empty(), new HashMap<>());
041    }
042    
043    public <T> boolean evaluateDisableConditions(ModelItem definition, String dataPath, T object) throws UndefinedItemPathException, BadItemTypeException
044    {
045        return evaluateDisableConditions(definition, definition.getDisableConditions(), dataPath, Optional.empty(), Map.of(), Optional.ofNullable(object), new HashMap<>());
046    }
047    
048    public <T> boolean evaluateDisableConditions(ModelItem definition, String dataPath, Optional<String> oldDataPath, Map<String, Object> values, T object, Map<String, Object> contextualParameters) throws UndefinedItemPathException, BadItemTypeException
049    {
050        return evaluateDisableConditions(definition, definition.getDisableConditions(), dataPath, oldDataPath, values, Optional.ofNullable(object), contextualParameters);
051    }
052    
053    /**
054     * Recursively evaluate the given {@link DisableConditions} against the given values
055     * @param definition the definition of the evaluated data
056     * @param disableConditions the conditions to evaluate
057     * @param dataPath the path of the evaluated data. Needed to get the value to compare as condition ids are relative to this one
058     * @param oldDataPath the old path of the evaluated data. Needed to get stored value if the data has been moved
059     * @param values values to check conditions on 
060     * @param object the object holding the data to evaluate and the condition value
061     * @param contextualParameters the contextual parameters
062     * @param <T> Type of object holding the data to evaluate
063     * @return <code>true</code> if the disable conditions are <code>true</code>, <code>false</code> otherwise
064     * @throws UndefinedItemPathException If no item is found corresponding to one of the given conditions
065     * @throws BadItemTypeException If the item referenced by one of the conditions is not an element
066     */
067    protected <T> boolean evaluateDisableConditions(ModelItem definition, DisableConditions disableConditions, String dataPath, Optional<String> oldDataPath, Map<String, Object> values, Optional<T> object, Map<String, Object> contextualParameters) throws UndefinedItemPathException, BadItemTypeException
068    {
069        if (!ModelHelper.hasDisableConditions(disableConditions))
070        {
071            return false;
072        }
073        
074        boolean andOperator = disableConditions.getAssociationType() == DisableConditions.ASSOCIATION_TYPE.AND;
075        
076        // initial value depends on OR or AND associations
077        boolean disabled = andOperator;
078        
079        for (DisableConditions subConditions : disableConditions.getSubConditions())
080        {
081            boolean result = evaluateDisableConditions(definition, subConditions, dataPath, oldDataPath, values, object, contextualParameters);
082            if (_resultIsSufficient(andOperator, result))
083            {
084                return result;
085            }
086            else
087            {
088                disabled = andOperator ? disabled && result : disabled || result;
089            }
090        }
091        
092        for (DisableCondition condition : disableConditions.getConditions())
093        {
094            boolean result = condition.evaluate(definition, dataPath, oldDataPath, values, object, contextualParameters);
095            if (_resultIsSufficient(andOperator, result))
096            {
097                return result;
098            }
099            else
100            {
101                disabled = andOperator ? disabled && result : disabled || result;
102            }
103        }
104                
105        return disabled;
106    }
107    
108    /**
109     * Check if the given result is sufficient to evaluate the current disable condition 
110     * @param andOperator the conditions operator
111     * @param result the result of the current condition
112     * @return <code>true</code> if the result of evaluation is sufficient, <code>false</code> otherwise
113     */
114    protected boolean _resultIsSufficient(boolean andOperator, boolean result)
115    {
116        return andOperator && !result || !andOperator && result;
117    }
118}