View Javadoc
1   /**
2    * This file is part of Indicators.
3    *
4    * Indicators is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * Indicators is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
16   */
17  package fr.inrae.agroclim.indicators.model.criteria;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import fr.inrae.agroclim.indicators.exception.IndicatorsException;
27  import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
28  import fr.inrae.agroclim.indicators.model.Parameter;
29  import fr.inrae.agroclim.indicators.model.data.DailyData;
30  import fr.inrae.agroclim.indicators.model.data.Resource;
31  import fr.inrae.agroclim.indicators.model.data.Variable;
32  import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
33  import fr.inrae.agroclim.indicators.util.Doublet;
34  import lombok.EqualsAndHashCode;
35  import lombok.Getter;
36  import lombok.NonNull;
37  import lombok.Setter;
38  
39  /**
40   * Contains multiple criteria.
41   *
42   * Last changed : $Date$
43   *
44   * @author $Author$
45   * @version $Revision$
46   */
47  @EqualsAndHashCode(
48          callSuper = true,
49          of = {"criteria", "logicalOperator"}
50          )
51  public final class CompositeCriteria extends Criteria {
52      /**
53       * UUID for Serialize.
54       */
55      private static final long serialVersionUID = 8096714573528536936L;
56  
57      /**
58       * Criteria the composite criteria contains.
59       */
60      @Getter
61      @Setter
62      private List<Criteria> criteria;
63  
64      /**
65       * The operator linking the criteria, AND by default.
66       */
67      @Setter
68      @NonNull
69      private LogicalOperator logicalOperator = LogicalOperator.AND;
70  
71      /**
72       * Constructor.
73       */
74      public CompositeCriteria() {
75          super();
76      }
77  
78      @Override
79      public CompositeCriteria clone() {
80          CompositeCriteria clone = new CompositeCriteria();
81          clone.criteria = new ArrayList<>();
82          criteria.forEach(clone.criteria::add);
83          clone.logicalOperator = logicalOperator;
84          return clone;
85      }
86  
87      @Override
88      public boolean eval(final DailyData dailyData) throws IndicatorsException {
89          if (logicalOperator == null) {
90              throw new IndicatorsException(ComputationErrorType.WRONG_DEFINITION, "The logical operator must be set!");
91          }
92          switch (logicalOperator) {
93          case AND:
94              for (final Criteria aCriteria : criteria) {
95                  if (!aCriteria.eval(dailyData)) {
96                      return false;
97                  }
98              }
99              return true;
100         case OR:
101             for (final Criteria aCriteria : criteria) {
102                 if (aCriteria.eval(dailyData)) {
103                     return true;
104                 }
105             }
106             return false;
107         case XOR:
108             int nbTrue = 0;
109             for (final Criteria aCriteria : criteria) {
110                 if (aCriteria.eval(dailyData)) {
111                     nbTrue++;
112                 }
113             }
114             return nbTrue == 1;
115         default:
116             throw new RuntimeException("The logical operator is not handled: "
117                     + logicalOperator.name());
118         }
119     }
120 
121     /**
122      * @param data
123      *            climatic data
124      * @return formula
125      * @throws IndicatorsException
126      *             exception while getting data
127      */
128     public String getFormula(final ClimaticDailyData data) throws IndicatorsException {
129         StringBuilder sb = new StringBuilder();
130         boolean first = true;
131         for (final Criteria aCriteria : criteria) {
132             sb.append("(");
133             if (aCriteria instanceof SimpleCriteria crit) {
134                 sb.append(crit.getFormula(data));
135             } else if (aCriteria instanceof CompositeCriteria crit) {
136                 sb.append(crit.getFormula(data));
137             }
138             sb.append(")");
139             if (first) {
140                 sb.append(" && ");
141                 first = false;
142             }
143         }
144         return sb.toString();
145     }
146 
147     /**
148      * @return Logical operator to connect criteria.
149      */
150     public LogicalOperator getLogicalOperator() {
151         return logicalOperator;
152     }
153 
154     @Override
155     public List<Doublet<Parameter, Number>> getParameterDefaults() {
156         final List<Doublet<Parameter, Number>> val = new ArrayList<>();
157         criteria.forEach(i -> val.addAll(i.getParameterDefaults()));
158         return val;
159     }
160 
161     @Override
162     public List<Parameter> getParameters() {
163         List<Parameter> parameters = new ArrayList<>();
164         criteria.stream()
165             .filter(crit -> crit.getParameters() != null)
166             .forEach(crit ->
167             crit.getParameters().forEach(param -> {
168                 if (!parameters.contains(param)) {
169                     parameters.add(param);
170                 }
171             })
172         );
173         return parameters;
174     }
175 
176     @Override
177     public Map<String, Double> getParametersValues() {
178         final Map<String, Double> val = new HashMap<>();
179         criteria.forEach(crit -> val.putAll(crit.getParametersValues()));
180         return val;
181     }
182 
183     @Override
184     public Set<Variable> getVariables() {
185         Set<Variable> variables = new HashSet<>();
186         if (criteria == null) {
187             return variables;
188         }
189         criteria.forEach(crit -> variables.addAll(crit.getVariables()));
190         return variables;
191     }
192 
193     @Override
194     public boolean isComputable(final Resource<? extends DailyData> res) {
195         for (final Criteria aCriteria : criteria) {
196             if (!aCriteria.isComputable(res)) {
197                 return false;
198             }
199         }
200         return true;
201     }
202 
203     @Override
204     public void setParametersValues(final Map<String, Double> values) {
205         criteria.forEach(crit -> crit.setParametersValues(values));
206     }
207 
208     @Override
209     public String toStringTree(final String indent) {
210         StringBuilder sb = new StringBuilder();
211         sb.append(indent).append("  class: ").append(getClass().getName())
212         .append("\n");
213         sb.append(indent).append("  logicalOperator: ");
214         if (logicalOperator != null) {
215             sb.append(logicalOperator.name());
216         }
217         sb.append("\n");
218         sb.append(indent).append("  criteria:\n");
219         criteria.forEach(crit -> sb.append(crit.toStringTree(indent + "  ")));
220         return sb.toString();
221     }
222 
223     @Override
224     public void removeParameter(final Parameter param) {
225         for (final Criteria c : this.criteria) {
226             if (c.getParameters().contains(param)) {
227                 c.getParameters().remove(param);
228             }
229         }
230     }
231 
232 }