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.indicator;
18  
19  import java.io.Serializable;
20  import java.text.NumberFormat;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.Objects;
26  
27  import javax.swing.event.EventListenerList;
28  
29  import fr.inrae.agroclim.indicators.model.Computable;
30  import fr.inrae.agroclim.indicators.model.EvaluationType;
31  import fr.inrae.agroclim.indicators.model.HasParameters;
32  import fr.inrae.agroclim.indicators.model.Knowledge;
33  import fr.inrae.agroclim.indicators.model.LocalizedString;
34  import fr.inrae.agroclim.indicators.model.Nameable;
35  import fr.inrae.agroclim.indicators.model.Note;
36  import fr.inrae.agroclim.indicators.model.Parameter;
37  import fr.inrae.agroclim.indicators.model.Quantifiable;
38  import fr.inrae.agroclim.indicators.model.TimeScale;
39  import fr.inrae.agroclim.indicators.model.Unit;
40  import fr.inrae.agroclim.indicators.model.data.UseVariables;
41  import fr.inrae.agroclim.indicators.model.function.normalization.NormalizationFunction;
42  import fr.inrae.agroclim.indicators.model.indicator.listener.HasIndicatorListener;
43  import fr.inrae.agroclim.indicators.model.indicator.listener.IndicatorEvent;
44  import fr.inrae.agroclim.indicators.model.indicator.listener.IndicatorListener;
45  import jakarta.xml.bind.annotation.XmlAccessType;
46  import jakarta.xml.bind.annotation.XmlAccessorType;
47  import jakarta.xml.bind.annotation.XmlElement;
48  import jakarta.xml.bind.annotation.XmlElementWrapper;
49  import jakarta.xml.bind.annotation.XmlIDREF;
50  import jakarta.xml.bind.annotation.XmlSeeAlso;
51  import jakarta.xml.bind.annotation.XmlTransient;
52  import jakarta.xml.bind.annotation.XmlType;
53  import lombok.AccessLevel;
54  import lombok.EqualsAndHashCode;
55  import lombok.Getter;
56  import lombok.NonNull;
57  import lombok.Setter;
58  import lombok.extern.log4j.Log4j2;
59  
60  /**
61   * Ancestor of all indicators.
62   *
63   * Last changed : $Date$
64   *
65   * @author jucufi
66   * @author $Author$
67   * @version $Revision$
68   */
69  @EqualsAndHashCode(
70          callSuper = false,
71          of = {"category", "id", "timescale", "parent"}
72          )
73  @Log4j2
74  @XmlAccessorType(XmlAccessType.FIELD)
75  @XmlSeeAlso({SimpleIndicator.class, CompositeIndicator.class})
76  @XmlType(propOrder = {"descriptions", "names", "id", "category", "color",
77          "timescale", "normalizationFunction", "parameters", "notes", "unit"})
78  public abstract class Indicator implements Cloneable,
79  Computable, HasIndicatorListener, HasParameters, Nameable, Quantifiable,
80  Serializable, UseVariables {
81  
82      /**
83       * UUID for Serializable.
84       */
85      private static final long serialVersionUID = 6030595237342422007L;
86  
87      /**
88       * Tag of IndicatorCategory.
89       *
90       * Category (level) of indicator (evaluation ⮕ simple climatic indicator).
91       */
92      @XmlElement
93      @Getter
94      @Setter
95      private String category;
96  
97      /**
98       * Color for the whole phase in the Getari graph.
99       */
100     @XmlElement
101     @Getter
102     @Setter
103     private String color;
104 
105     /**
106      * Localized descriptions.
107      */
108     @XmlElement(name = "description")
109     @Getter
110     @Setter
111     private List<LocalizedString> descriptions;
112 
113     /**
114      * String ID.
115      */
116     @XmlElement(required = true)
117     @Getter
118     @Setter
119     private String id;
120 
121     /**
122      * If the indicator is computable with the resource (needed variable,
123      * instance properties, ...).
124      */
125     @XmlTransient
126     @Getter
127     @Setter
128     private boolean isComputable = true;
129 
130     /**
131      * List for all listeners.
132      */
133     @XmlTransient
134     @Getter(AccessLevel.PROTECTED)
135     private final EventListenerList listeners = new EventListenerList();
136 
137     /**
138      * Localized names.
139      */
140     @XmlElement(name = "name")
141     @Getter(AccessLevel.PUBLIC)
142     @Setter
143     private List<LocalizedString> names;
144 
145     /**
146      * Function to normalize between 0 and 1.
147      */
148     @XmlElement
149     @Getter
150     @Setter
151     private NormalizationFunction normalizationFunction;
152 
153     /**
154      * Raw value before applying normalization function.
155      */
156     @XmlTransient
157     @Getter
158     @Setter
159     private Double notNormalizedValue;
160 
161     /**
162      * Parameters for indicator.
163      */
164     @XmlElementWrapper(name = "parameters")
165     @XmlElement(name = "parameter")
166     @Getter
167     @Setter
168     private List<Parameter> parameters;
169 
170     /**
171      * Parent indicator (composite or phase or evaluation).
172      */
173     @XmlTransient
174     @Getter
175     @Setter
176     private Indicator parent;
177 
178     /**
179      * Timescale of climatic data.
180      */
181     @Getter
182     @Setter
183     private TimeScale timescale = TimeScale.DAILY;
184 
185     /**
186      * Normalized value.
187      */
188     @XmlTransient
189     @Getter
190     @Setter
191     private Double value;
192 
193     /**
194      * References of notes indicator.
195      */
196     @XmlElement(name = "note")
197     @Getter
198     @Setter
199     @XmlIDREF
200     private List<Note> notes;
201 
202     /**
203      * Measurement unit of the indicator, defined for the case of climatic variables with common units.
204      *
205      * Only set for the indicators provided in Knowledge.
206      */
207     @XmlElement(name = "unit")
208     @Getter
209     @Setter
210     @XmlIDREF
211     private Unit unit;
212 
213     /**
214      * Constructor.
215      */
216     protected Indicator() {
217     }
218 
219     @Override
220     public final void addIndicatorListener(final IndicatorListener listener) {
221         if (getIndicatorListeners() != null) {
222             for (final IndicatorListener l : getIndicatorListeners()) {
223                 if (listener.equals(l)) {
224                     return;
225                 }
226             }
227         }
228         listeners.add(IndicatorListener.class, listener);
229         if (this instanceof CompositeIndicator compositeIndicator) {
230             compositeIndicator.getIndicators()
231             .forEach(i -> i.addIndicatorListener(listener));
232         }
233     }
234 
235     @Override
236     @SuppressWarnings("checkstyle:DesignForExtension")
237     public Indicator clone() throws CloneNotSupportedException {
238         final Indicator clone = (Indicator) super.clone();
239         clone.category = category;
240         clone.color = color;
241         if (descriptions != null) {
242             clone.descriptions = new ArrayList<>();
243             for (final LocalizedString description : descriptions) {
244                 clone.descriptions.add(description.clone());
245             }
246         }
247         clone.id = id;
248         // Do not clone indicatorValueListener
249         clone.isComputable = isComputable;
250         if (names != null) {
251             clone.names = new ArrayList<>();
252             for (final LocalizedString name : names) {
253                 clone.names.add(name.clone());
254             }
255         }
256         if (normalizationFunction != null) {
257             clone.normalizationFunction = normalizationFunction.clone();
258         }
259         clone.notNormalizedValue = notNormalizedValue;
260         if (parameters != null) {
261             clone.parameters = new ArrayList<>();
262             for (final Parameter param : parameters) {
263                 clone.parameters.add(param.clone());
264             }
265         }
266         // do not clone parent or loop on clone...
267         clone.parent = parent;
268         clone.value = value;
269         return clone;
270     }
271 
272     @Override
273     public final void fireIndicatorEvent(final IndicatorEvent event) {
274         LOGGER.traceEntry("{} : {} {}", getId(), event.getSource().getId(),
275                 event.getAssociatedType());
276         if (getIndicatorListeners() == null
277                 || getIndicatorListeners().length == 0) {
278             if (getParent() != null) {
279                 getParent().fireIndicatorEvent(event);
280             }
281             return;
282         }
283         for (final IndicatorListener listener : getIndicatorListeners()) {
284             listener.onIndicatorEvent(event);
285         }
286     }
287 
288     /**
289      * Notify listeners that indicator value has changed.
290      */
291     public abstract void fireValueUpdated();
292 
293     /**
294      * @param languageCode lang code
295      * @return description for the lang
296      */
297     public final String getDescription(@NonNull final String languageCode) {
298         return LocalizedString.getString(descriptions, languageCode);
299     }
300 
301     /**
302      * @return indicator category according to tag
303      */
304     public final IndicatorCategory getIndicatorCategory() {
305         return IndicatorCategory.getByTag(category);
306     }
307 
308     @Override
309     public final IndicatorListener[] getIndicatorListeners() {
310         return listeners.getListeners(IndicatorListener.class);
311     }
312 
313     @Override
314     public final String getName() {
315         final String langCode = Locale.getDefault().getLanguage();
316         return getName(langCode);
317     }
318 
319     @Override
320     public final String getName(final Locale locale) {
321         final String langCode = locale.getLanguage();
322         return getName(langCode);
323     }
324 
325     /**
326      * @param languageCode lang code
327      * @return name for the lang
328      */
329     public final String getName(@NonNull final String languageCode) {
330         return LocalizedString.getString(names, languageCode);
331     }
332 
333     /**
334      * @return XML path of the indicator
335      */
336     public final String getPath() {
337         if (parent != null) {
338             return parent.getPath() + "/" + id;
339         }
340         return id;
341     }
342 
343     /**
344      * @param languageCode lang code
345      * @return description for the lang where parameters are replaced and
346      * formatted
347      */
348     public final String getPrettyDescription(
349             @NonNull final String languageCode) {
350         String description = getDescription(languageCode);
351         final Locale locale = Locale.forLanguageTag(languageCode);
352         final NumberFormat nf = NumberFormat.getInstance(locale);
353         for (final Map.Entry<String, Double> entry : getParametersValues()
354                 .entrySet()) {
355             description = description.replace(
356                     "{" + entry.getKey() + "}",
357                     nf.format(entry.getValue())
358                     );
359         }
360         return description;
361     }
362 
363     /**
364      * @return Evaluation type.
365      */
366     public abstract EvaluationType getType();
367 
368     /**
369      * @param indicatorCategory indicator category
370      */
371     public final void setIndicatorCategory(
372             @NonNull final IndicatorCategory indicatorCategory) {
373         category = indicatorCategory.getTag();
374     }
375 
376     /**
377      * @param languageCode lang code
378      * @param name for the lang
379      */
380     public final void setName(
381             @NonNull final String languageCode, final String name) {
382         if (names == null) {
383             names = new ArrayList<>();
384         }
385         for (final LocalizedString string : names) {
386             if (Objects.equals(string.getLang(), languageCode)) {
387                 string.setValue(name);
388                 return;
389             }
390         }
391         final LocalizedString string = new LocalizedString();
392         string.setLang(languageCode);
393         string.setValue(name);
394         names.add(string);
395     }
396 
397     /**
398      * Set parameters (id and attributes) for the indicator and its criteria
399      * from knowledge.
400      *
401      * @param knowledge reference definition of indicators
402      */
403     public abstract void setParametersFromKnowledge(Knowledge knowledge);
404 
405     @Override
406     public final String toString() {
407         return getName();
408     }
409 
410     /**
411      * @param indent indentation string
412      * @return Structured string representation.
413      */
414     public abstract String toStringTree(String indent);
415 
416     /**
417      * @param indent indentation string
418      * @return Structured string representation.
419      */
420     protected final String toStringTreeBase(final String indent) {
421         final StringBuilder sb = new StringBuilder();
422         sb.append(indent).append("  id: ").append(getId()).append("\n");
423         sb.append(indent).append("  name: ").append(getName()).append("\n");
424         sb.append(indent).append("  class: ").append(getClass().getName())
425         .append("\n");
426         sb.append(indent).append("  category: ").append(category).append("\n");
427         return sb.toString();
428     }
429 }