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;
18  
19  import java.io.InputStream;
20  import java.util.ArrayList;
21  import java.util.EnumMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import fr.inrae.agroclim.indicators.exception.IndicatorsException;
26  import fr.inrae.agroclim.indicators.model.criteria.ComparisonCriteria;
27  import fr.inrae.agroclim.indicators.model.criteria.CompositeCriteria;
28  import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
29  import fr.inrae.agroclim.indicators.model.criteria.LogicalOperator;
30  import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
31  import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
32  import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
33  import fr.inrae.agroclim.indicators.model.function.aggregation.AggregationFunction;
34  import fr.inrae.agroclim.indicators.model.function.aggregation.JEXLFunction;
35  import fr.inrae.agroclim.indicators.model.function.normalization.Exponential;
36  import fr.inrae.agroclim.indicators.model.function.normalization.Linear;
37  import fr.inrae.agroclim.indicators.model.function.normalization.Normal;
38  import fr.inrae.agroclim.indicators.model.function.normalization.NormalizationFunction;
39  import fr.inrae.agroclim.indicators.model.function.normalization.Sigmoid;
40  import fr.inrae.agroclim.indicators.model.indicator.Average;
41  import fr.inrae.agroclim.indicators.model.indicator.AverageOfDiff;
42  import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
43  import fr.inrae.agroclim.indicators.model.indicator.DayOfYear;
44  import fr.inrae.agroclim.indicators.model.indicator.DiffOfSum;
45  import fr.inrae.agroclim.indicators.model.indicator.Formula;
46  import fr.inrae.agroclim.indicators.model.indicator.Indicator;
47  import fr.inrae.agroclim.indicators.model.indicator.IndicatorCategory;
48  import fr.inrae.agroclim.indicators.model.indicator.InjectedParameter;
49  import fr.inrae.agroclim.indicators.model.indicator.Max;
50  import fr.inrae.agroclim.indicators.model.indicator.MaxWaveLength;
51  import fr.inrae.agroclim.indicators.model.indicator.Min;
52  import fr.inrae.agroclim.indicators.model.indicator.NumberOfDays;
53  import fr.inrae.agroclim.indicators.model.indicator.NumberOfWaves;
54  import fr.inrae.agroclim.indicators.model.indicator.PotentialSowingDaysFrequency;
55  import fr.inrae.agroclim.indicators.model.indicator.Quotient;
56  import fr.inrae.agroclim.indicators.model.indicator.SimpleIndicator;
57  import fr.inrae.agroclim.indicators.model.indicator.Sum;
58  import fr.inrae.agroclim.indicators.model.indicator.Tamm;
59  import fr.inrae.agroclim.indicators.xml.XMLUtil;
60  import jakarta.xml.bind.annotation.XmlAccessType;
61  import jakarta.xml.bind.annotation.XmlAccessorType;
62  import jakarta.xml.bind.annotation.XmlAttribute;
63  import jakarta.xml.bind.annotation.XmlElement;
64  import jakarta.xml.bind.annotation.XmlElementWrapper;
65  import jakarta.xml.bind.annotation.XmlRootElement;
66  import lombok.Getter;
67  import lombok.NonNull;
68  import lombok.Setter;
69  import lombok.ToString;
70  import lombok.extern.log4j.Log4j2;
71  
72  /**
73   * knowledge.xml.
74   *
75   * @author Olivier Maury
76   */
77  @XmlRootElement
78  @XmlAccessorType(XmlAccessType.FIELD)
79  @Log4j2
80  @ToString
81  public final class Knowledge implements Cloneable {
82      /**
83       * The classes needed to load XML knowledge file.
84       */
85      public static final Class<?>[] CLASSES_FOR_JAXB = {Knowledge.class,
86              LocalizedString.class, Parameter.class, TimeScale.class, Note.class,
87              /*- Indicators */
88              Average.class, AverageOfDiff.class, CompositeIndicator.class, DayOfYear.class, DiffOfSum.class,
89              Formula.class, Indicator.class, InjectedParameter.class, Max.class, MaxWaveLength.class, Min.class,
90              Normal.class, NumberOfDays.class, NumberOfWaves.class,
91              PotentialSowingDaysFrequency.class, Quotient.class,
92              SimpleIndicator.class, Sum.class, Tamm.class,
93              /*- Normalisation */
94              AggregationFunction.class, Exponential.class, JEXLFunction.class,
95              Linear.class, NormalizationFunction.class, Sigmoid.class,
96              /*- Criteria */
97              ComparisonCriteria.class, CompositeCriteria.class, FormulaCriteria.class,
98              LogicalOperator.class, NoCriteria.class, RelationalOperator.class,
99              SimpleCriteria.class};
100 
101     /**
102      * URL of knowledge.xml.
103      */
104     public static final Map<TimeScale, String> RESOURCES;
105 
106     static {
107         RESOURCES = new EnumMap<>(TimeScale.class);
108         RESOURCES.put(TimeScale.DAILY, "/fr/inrae/agroclim/indicators/knowledge.xml");
109         RESOURCES.put(TimeScale.HOURLY, "/fr/inrae/agroclim/indicators/knowledge_hourly.xml");
110     }
111 
112     /**
113      * Loop on indicator list to get the Indicator defined its id.
114      *
115      * @param indicatorId indicator id
116      * @param indicators indicator list
117      * @return indicator matching id or null
118      */
119     private static Indicator getIndicator(@NonNull final String indicatorId,
120             final List<? extends Indicator> indicators) {
121         Indicator result = null;
122         for (final Indicator i : indicators) {
123             if (indicatorId.equals(i.getId())) {
124                 result = i;
125                 break;
126             }
127         }
128         return result;
129     }
130 
131     /**
132      * Deserialize Knowledge from embedded file for daily indicators.
133      *
134      * @return deserialized Knowledge
135      * @throws IndicatorsException error while loading knowledge
136      */
137     public static Knowledge load() throws IndicatorsException {
138         return load(TimeScale.DAILY);
139     }
140 
141     /**
142      * Deserialize Knowledge from input stream.
143      *
144      * @param stream
145      *            input stream
146      * @return deserialized Knowledge
147      * @throws IndicatorsException
148      *             error while loading knowledge
149      */
150     private static Knowledge load(final InputStream stream) throws IndicatorsException {
151         try {
152             final Knowledge knowledge = (Knowledge) XMLUtil.loadResource(stream,
153                     CLASSES_FOR_JAXB);
154             knowledge.ecophysiologicalProcesses.forEach(ind ->
155             ind.setIndicatorCategory(IndicatorCategory.ECOPHYSIOLOGICAL_PROCESSES));
156             knowledge.indicators.forEach(ind ->
157             ind.setIndicatorCategory(IndicatorCategory.CLIMATIC_EFFECTS));
158             knowledge.culturalPractices.forEach(ind ->
159             ind.setIndicatorCategory(IndicatorCategory.CULTURAL_PRATICES));
160             return knowledge;
161         } catch (final IndicatorsException ex) {
162             LOGGER.error(ex);
163             throw ex;
164         }
165     }
166 
167     /**
168      * Deserialize Knowledge from embedded file.
169      *
170      * @param timescale timescale of indicators
171      * @return deserialized Knowledge
172      * @throws IndicatorsException error while loading knowledge
173      */
174     public static Knowledge load(final TimeScale timescale) throws IndicatorsException {
175         final InputStream stream = Knowledge.class.getResourceAsStream(RESOURCES.get(timescale));
176         return load(stream);
177     }
178 
179     /**
180      * Indicators for cultural practices.
181      */
182     @XmlElementWrapper(name = "culturalPractices")
183     @XmlElement(name = "culturalPractice")
184     @Setter
185     private List<CompositeIndicator> culturalPractices;
186 
187     /**
188      * Indicators for ecophysiological processes.
189      */
190     @XmlElementWrapper(name = "ecophysiologicalProcesses")
191     @XmlElement(name = "ecophysiologicalProcess")
192     @Setter
193     private List<CompositeIndicator> ecophysiologicalProcesses;
194 
195     /**
196      * Parameters for indicator and indicator criteria.
197      */
198     @XmlElementWrapper(name = "parameters")
199     @XmlElement(name = "parameter")
200     @Getter
201     @Setter
202     private List<Parameter> parameters;
203 
204     /**
205      * Timescale of indicators.
206      */
207     @Getter
208     @Setter
209     @XmlAttribute
210     private TimeScale timescale = TimeScale.DAILY;
211 
212     /**
213      * Notes library for indicators.
214      */
215     @XmlElementWrapper(name = "notes")
216     @XmlElement(name = "note")
217     @Getter
218     @Setter
219     private List<Note> notes;
220 
221     /**
222      * Measurement units for indicators.
223      */
224     @XmlElementWrapper(name = "units")
225     @XmlElement(name = "unit")
226     @Getter
227     @Setter
228     private List<Unit> units;
229 
230     /**
231      * Indicators for climatic effets.
232      */
233     @XmlElementWrapper(name = "climaticEffects")
234     @XmlElement(name = "climaticEffect")
235     @Getter
236     @Setter
237     private List<CompositeIndicator> indicators;
238 
239     /**
240      * Constructor.
241      */
242     public Knowledge() {
243         culturalPractices = new ArrayList<>();
244         ecophysiologicalProcesses = new ArrayList<>();
245         indicators = new ArrayList<>();
246     }
247 
248     @Override
249     public Knowledge clone() {
250         final Knowledge clone = new Knowledge();
251         culturalPractices.forEach(practice -> clone.culturalPractices.add(practice.clone()));
252         ecophysiologicalProcesses.forEach(process -> clone.ecophysiologicalProcesses.add(process.clone()));
253         indicators.forEach(clone.indicators::add);
254         return clone;
255     }
256 
257     /**
258      * @param indicatorId indicator id
259      * @return indicator in climatic effects category
260      */
261     private Indicator getClimaticEffectsIndicator(final String indicatorId) {
262         return getIndicator(indicatorId, indicators);
263     }
264 
265     /**
266      * @return indicators in cultural pratices category
267      */
268     public List<CompositeIndicator> getCulturalPractices() {
269         return culturalPractices;
270     }
271 
272     /**
273      * @return indicators in ecophysiological processes category
274      */
275     public List<CompositeIndicator> getEcophysiologicalProcesses() {
276         return ecophysiologicalProcesses;
277     }
278 
279     /**
280      * Loop on indicator in knowledge to get the Indicator defined its id.
281      *
282      * @param indicatorId indicator id
283      * @return indicator matching id or null
284      */
285     public Indicator getIndicator(@NonNull final String indicatorId) {
286         Indicator result;
287         for (final CompositeIndicator indicator : indicators) {
288             if (indicatorId.equals(indicator.getId())) {
289                 return indicator;
290             }
291             result = getIndicator(indicatorId, indicator.getIndicators());
292             if (result != null) {
293                 result.setParent(indicator);
294                 return result;
295             }
296         }
297         for (final CompositeIndicator indicator : culturalPractices) {
298             if (indicatorId.equals(indicator.getId())) {
299                 return indicator;
300             }
301             result = getIndicator(indicatorId, indicator.getIndicators());
302             if (result != null) {
303                 result.setParent(indicator);
304                 return result;
305             }
306         }
307         for (final CompositeIndicator indicator : ecophysiologicalProcesses) {
308             if (indicatorId.equals(indicator.getId())) {
309                 return indicator;
310             }
311             result = getIndicator(indicatorId, indicator.getIndicators());
312             if (result != null) {
313                 result.setParent(indicator);
314                 return result;
315             }
316         }
317         return null;
318     }
319 
320     /**
321      * Get child indicators for the CLIMATIC indicator or the category.
322      *
323      * @param ind indicator with sub indicators
324      * @return child indicators for the category
325      */
326     public List<? extends Indicator> getNextIndicators(
327             final CompositeIndicator ind) {
328         final IndicatorCategory category = ind.getIndicatorCategory();
329         final String id = ind.getId();
330         if (category == null) {
331             throw new IllegalArgumentException("No indicator category for "
332                     + id);
333         }
334         switch (category) {
335         case PHENO_PHASES:
336             return getEcophysiologicalProcesses();
337         case CULTURAL_PRATICES:
338             if (ind.isPhase()) {
339                 return getCulturalPractices();
340             }
341             return getIndicators();
342         case ECOPHYSIOLOGICAL_PROCESSES:
343             if (ind.isPhase()) {
344                 return getEcophysiologicalProcesses();
345             }
346             return getIndicators();
347         case CLIMATIC_EFFECTS:
348             return ((CompositeIndicator) getClimaticEffectsIndicator(id))
349                     .getIndicators();
350         default:
351             LOGGER.fatal("Not handled category: " + category);
352             throw new IllegalArgumentException("Not handled category: "
353                     + category);
354         }
355     }
356 
357     public Parameter getParameterById(final String id) {
358         return this.parameters.stream()
359                 .filter(parameter -> parameter.getId().equals(id))
360                 .findFirst().orElse(null);
361     }
362     /**
363      * Update localized description and name of indicator from Knowledge.
364      *
365      * @param indicator indicator to update
366      */
367     public void setI18n(final Indicator indicator) {
368         if (indicator.getId() == null) {
369             throw new IllegalArgumentException("indicator.id must not be null! "
370                     + indicator.getId());
371         }
372         final Indicator ind = getIndicator(indicator.getId());
373         if (ind != null) {
374             if (indicator.getNames() == null) {
375                 indicator.setNames(new ArrayList<>());
376             } else {
377                 indicator.getNames().clear();
378             }
379             if (ind.getNames() != null) {
380                 indicator.getNames().addAll(ind.getNames());
381             }
382             if (indicator.getDescriptions() == null) {
383                 indicator.setDescriptions(new ArrayList<>());
384             } else {
385                 indicator.getDescriptions().clear();
386             }
387             if (ind.getDescriptions() != null) {
388                 indicator.getDescriptions().addAll(ind.getDescriptions());
389             }
390         }
391         if (indicator instanceof CompositeIndicator) {
392             ((CompositeIndicator) indicator).getIndicators().forEach(this::setI18n);
393         }
394     }
395 }