Indicator.java
/*
* This file is part of Indicators.
*
* Indicators is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Indicators is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Indicators. If not, see <https://www.gnu.org/licenses/>.
*/
package fr.inrae.agroclim.indicators.model.indicator;
import java.io.Serializable;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.swing.event.EventListenerList;
import fr.inrae.agroclim.indicators.model.Computable;
import fr.inrae.agroclim.indicators.model.EvaluationType;
import fr.inrae.agroclim.indicators.model.HasParameters;
import fr.inrae.agroclim.indicators.model.Knowledge;
import fr.inrae.agroclim.indicators.model.LocalizedString;
import fr.inrae.agroclim.indicators.model.Nameable;
import fr.inrae.agroclim.indicators.model.Note;
import fr.inrae.agroclim.indicators.model.Parameter;
import fr.inrae.agroclim.indicators.model.Quantifiable;
import fr.inrae.agroclim.indicators.model.TimeScale;
import fr.inrae.agroclim.indicators.model.Unit;
import fr.inrae.agroclim.indicators.model.data.UseVariables;
import fr.inrae.agroclim.indicators.model.function.normalization.NormalizationFunction;
import fr.inrae.agroclim.indicators.model.indicator.listener.HasIndicatorListener;
import fr.inrae.agroclim.indicators.model.indicator.listener.IndicatorEvent;
import fr.inrae.agroclim.indicators.model.indicator.listener.IndicatorListener;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlIDREF;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
/**
* Ancestor of all indicators.
*
* Last changed : $Date$
*
* @author jucufi
* @author $Author$
* @version $Revision$
*/
@EqualsAndHashCode(
callSuper = false,
of = {"category", "id", "timescale", "parent"}
)
@Log4j2
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({SimpleIndicator.class, CompositeIndicator.class})
@XmlType(propOrder = {"descriptions", "names", "id", "category", "color",
"timescale", "normalizationFunction", "parameters", "notes", "unit"})
public abstract class Indicator implements Cloneable,
Computable, HasIndicatorListener, HasParameters, Nameable, Quantifiable,
Serializable, UseVariables {
/**
* UUID for Serializable.
*/
private static final long serialVersionUID = 6030595237342422007L;
/**
* Tag of IndicatorCategory.
*
* Category (level) of indicator (evaluation ⮕ simple climatic indicator).
*/
@XmlElement
@Getter
@Setter
private String category;
/**
* Color for the whole phase in the Getari graph.
*/
@XmlElement
@Getter
@Setter
private String color;
/**
* Localized descriptions.
*/
@XmlElement(name = "description")
@Getter
@Setter
private List<LocalizedString> descriptions;
/**
* String ID.
*/
@XmlElement(required = true)
@Getter
@Setter
private String id;
/**
* If the indicator is computable with the resource (needed variable,
* instance properties, ...).
*/
@XmlTransient
@Getter
@Setter
private boolean isComputable = true;
/**
* List for all listeners.
*/
@XmlTransient
@Getter(AccessLevel.PROTECTED)
private final EventListenerList listeners = new EventListenerList();
/**
* Localized names.
*/
@XmlElement(name = "name")
@Getter(AccessLevel.PUBLIC)
@Setter
private List<LocalizedString> names;
/**
* Function to normalize between 0 and 1.
*/
@XmlElement
@Getter
@Setter
private NormalizationFunction normalizationFunction;
/**
* Raw value before applying normalization function.
*/
@XmlTransient
@Getter
@Setter
private Double notNormalizedValue;
/**
* Parameters for indicator.
*/
@XmlElementWrapper(name = "parameters")
@XmlElement(name = "parameter")
@Getter
@Setter
private List<Parameter> parameters;
/**
* Parent indicator (composite or phase or evaluation).
*/
@XmlTransient
@Getter
@Setter
private Indicator parent;
/**
* Timescale of climatic data.
*/
@Getter
@Setter
private TimeScale timescale = TimeScale.DAILY;
/**
* Normalized value.
*/
@XmlTransient
@Getter
@Setter
private Double value;
/**
* References of notes indicator.
*/
@XmlElement(name = "note")
@Getter
@Setter
@XmlIDREF
private List<Note> notes;
/**
* Measurement unit of the indicator, defined for the case of climatic variables with common units.
*
* Only set for the indicators provided in Knowledge.
*/
@XmlElement(name = "unit")
@Getter
@Setter
@XmlIDREF
private Unit unit;
/**
* Constructor.
*/
protected Indicator() {
}
@Override
public final void addIndicatorListener(final IndicatorListener listener) {
if (getIndicatorListeners() != null) {
for (final IndicatorListener l : getIndicatorListeners()) {
if (listener.equals(l)) {
return;
}
}
}
listeners.add(IndicatorListener.class, listener);
if (this instanceof CompositeIndicator compositeIndicator) {
compositeIndicator.getIndicators()
.forEach(i -> i.addIndicatorListener(listener));
}
}
@Override
@SuppressWarnings("checkstyle:DesignForExtension")
public Indicator clone() throws CloneNotSupportedException {
final Indicator clone = (Indicator) super.clone();
clone.category = category;
clone.color = color;
if (descriptions != null) {
clone.descriptions = new ArrayList<>();
for (final LocalizedString description : descriptions) {
clone.descriptions.add(description.clone());
}
}
clone.id = id;
// Do not clone indicatorValueListener
clone.isComputable = isComputable;
if (names != null) {
clone.names = new ArrayList<>();
for (final LocalizedString name : names) {
clone.names.add(name.clone());
}
}
if (normalizationFunction != null) {
clone.normalizationFunction = normalizationFunction.clone();
}
clone.notNormalizedValue = notNormalizedValue;
if (parameters != null) {
clone.parameters = new ArrayList<>();
for (final Parameter param : parameters) {
clone.parameters.add(param.clone());
}
}
// do not clone parent or loop on clone...
clone.parent = parent;
clone.value = value;
return clone;
}
@Override
public final void fireIndicatorEvent(final IndicatorEvent event) {
LOGGER.traceEntry("{} : {} {}", getId(), event.getSource().getId(),
event.getAssociatedType());
if (getIndicatorListeners() == null
|| getIndicatorListeners().length == 0) {
if (getParent() != null) {
getParent().fireIndicatorEvent(event);
}
return;
}
for (final IndicatorListener listener : getIndicatorListeners()) {
listener.onIndicatorEvent(event);
}
}
/**
* Notify listeners that indicator value has changed.
*/
public abstract void fireValueUpdated();
/**
* @param languageCode lang code
* @return description for the lang
*/
public final String getDescription(@NonNull final String languageCode) {
return LocalizedString.getString(descriptions, languageCode);
}
/**
* @return indicator category according to tag
*/
public final IndicatorCategory getIndicatorCategory() {
return IndicatorCategory.getByTag(category);
}
@Override
public final IndicatorListener[] getIndicatorListeners() {
return listeners.getListeners(IndicatorListener.class);
}
@Override
public final String getName() {
final String langCode = Locale.getDefault().getLanguage();
return getName(langCode);
}
@Override
public final String getName(final Locale locale) {
final String langCode = locale.getLanguage();
return getName(langCode);
}
/**
* @param languageCode lang code
* @return name for the lang
*/
public final String getName(@NonNull final String languageCode) {
return LocalizedString.getString(names, languageCode);
}
/**
* @return XML path of the indicator
*/
public final String getPath() {
if (parent != null) {
return parent.getPath() + "/" + id;
}
return id;
}
/**
* @param languageCode lang code
* @return description for the lang where parameters are replaced and
* formatted
*/
public final String getPrettyDescription(
@NonNull final String languageCode) {
String description = getDescription(languageCode);
final Locale locale = Locale.forLanguageTag(languageCode);
final NumberFormat nf = NumberFormat.getInstance(locale);
for (final Map.Entry<String, Double> entry : getParametersValues()
.entrySet()) {
description = description.replace(
"{" + entry.getKey() + "}",
nf.format(entry.getValue())
);
}
return description;
}
/**
* @return Evaluation type.
*/
public abstract EvaluationType getType();
/**
* @param indicatorCategory indicator category
*/
public final void setIndicatorCategory(
@NonNull final IndicatorCategory indicatorCategory) {
category = indicatorCategory.getTag();
}
/**
* @param languageCode lang code
* @param name for the lang
*/
public final void setName(
@NonNull final String languageCode, final String name) {
if (names == null) {
names = new ArrayList<>();
}
for (final LocalizedString string : names) {
if (Objects.equals(string.getLang(), languageCode)) {
string.setValue(name);
return;
}
}
final LocalizedString string = new LocalizedString();
string.setLang(languageCode);
string.setValue(name);
names.add(string);
}
/**
* Set parameters (id and attributes) for the indicator and its criteria
* from knowledge.
*
* @param knowledge reference definition of indicators
*/
public abstract void setParametersFromKnowledge(Knowledge knowledge);
@Override
public final String toString() {
return getName();
}
/**
* @param indent indentation string
* @return Structured string representation.
*/
public abstract String toStringTree(String indent);
/**
* @param indent indentation string
* @return Structured string representation.
*/
protected final String toStringTreeBase(final String indent) {
final StringBuilder sb = new StringBuilder();
sb.append(indent).append(" id: ").append(getId()).append("\n");
sb.append(indent).append(" name: ").append(getName()).append("\n");
sb.append(indent).append(" class: ").append(getClass().getName())
.append("\n");
sb.append(indent).append(" category: ").append(category).append("\n");
return sb.toString();
}
}