Knowledge.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;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import fr.inrae.agroclim.indicators.exception.IndicatorsException;
import fr.inrae.agroclim.indicators.model.criteria.ComparisonCriteria;
import fr.inrae.agroclim.indicators.model.criteria.CompositeCriteria;
import fr.inrae.agroclim.indicators.model.criteria.FormulaCriteria;
import fr.inrae.agroclim.indicators.model.criteria.LogicalOperator;
import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
import fr.inrae.agroclim.indicators.model.criteria.RelationalOperator;
import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
import fr.inrae.agroclim.indicators.model.function.aggregation.AggregationFunction;
import fr.inrae.agroclim.indicators.model.function.aggregation.JEXLFunction;
import fr.inrae.agroclim.indicators.model.function.normalization.Exponential;
import fr.inrae.agroclim.indicators.model.function.normalization.Linear;
import fr.inrae.agroclim.indicators.model.function.normalization.Normal;
import fr.inrae.agroclim.indicators.model.function.normalization.NormalizationFunction;
import fr.inrae.agroclim.indicators.model.function.normalization.Sigmoid;
import fr.inrae.agroclim.indicators.model.indicator.Average;
import fr.inrae.agroclim.indicators.model.indicator.AverageOfDiff;
import fr.inrae.agroclim.indicators.model.indicator.CompositeIndicator;
import fr.inrae.agroclim.indicators.model.indicator.DayOfYear;
import fr.inrae.agroclim.indicators.model.indicator.DiffOfSum;
import fr.inrae.agroclim.indicators.model.indicator.Formula;
import fr.inrae.agroclim.indicators.model.indicator.Indicator;
import fr.inrae.agroclim.indicators.model.indicator.IndicatorCategory;
import fr.inrae.agroclim.indicators.model.indicator.InjectedParameter;
import fr.inrae.agroclim.indicators.model.indicator.Max;
import fr.inrae.agroclim.indicators.model.indicator.MaxWaveLength;
import fr.inrae.agroclim.indicators.model.indicator.Min;
import fr.inrae.agroclim.indicators.model.indicator.NumberOfDays;
import fr.inrae.agroclim.indicators.model.indicator.NumberOfWaves;
import fr.inrae.agroclim.indicators.model.indicator.PotentialSowingDaysFrequency;
import fr.inrae.agroclim.indicators.model.indicator.Quotient;
import fr.inrae.agroclim.indicators.model.indicator.SimpleIndicator;
import fr.inrae.agroclim.indicators.model.indicator.Sum;
import fr.inrae.agroclim.indicators.model.indicator.Tamm;
import fr.inrae.agroclim.indicators.xml.XMLUtil;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
/**
* Object loaded from knowledge.xml.
*
* @author Olivier Maury
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@Log4j2
@ToString
public final class Knowledge implements Cloneable {
/**
* The classes needed to load XML knowledge file.
*/
public static final Class<?>[] CLASSES_FOR_JAXB = {Knowledge.class,
LocalizedString.class, Parameter.class, TimeScale.class, Note.class,
/*- Indicators */
Average.class, AverageOfDiff.class, CompositeIndicator.class, DayOfYear.class, DiffOfSum.class,
Formula.class, Indicator.class, InjectedParameter.class, Max.class, MaxWaveLength.class, Min.class,
Normal.class, NumberOfDays.class, NumberOfWaves.class,
PotentialSowingDaysFrequency.class, Quotient.class,
SimpleIndicator.class, Sum.class, Tamm.class,
/*- Normalization */
AggregationFunction.class, Exponential.class, JEXLFunction.class,
Linear.class, NormalizationFunction.class, Sigmoid.class,
/*- Criteria */
ComparisonCriteria.class, CompositeCriteria.class, FormulaCriteria.class,
LogicalOperator.class, NoCriteria.class, RelationalOperator.class,
SimpleCriteria.class};
/**
* URL of knowledge.xml.
*/
public static final Map<TimeScale, String> RESOURCES;
static {
RESOURCES = new EnumMap<>(TimeScale.class);
RESOURCES.put(TimeScale.DAILY, "/fr/inrae/agroclim/indicators/knowledge.xml");
RESOURCES.put(TimeScale.HOURLY, "/fr/inrae/agroclim/indicators/knowledge_hourly.xml");
}
/**
* Loop on indicator list to get the Indicator defined its id.
*
* @param indicatorId indicator id
* @param indicators indicator list
* @return indicator matching id or null
*/
private static Indicator getIndicator(@NonNull final String indicatorId,
final List<? extends Indicator> indicators) {
Indicator result = null;
for (final Indicator i : indicators) {
if (indicatorId.equals(i.getId())) {
result = i;
break;
}
}
return result;
}
/**
* Deserialize Knowledge from embedded file for daily indicators.
*
* @return deserialized Knowledge
* @throws IndicatorsException error while loading knowledge
*/
public static Knowledge load() throws IndicatorsException {
return load(TimeScale.DAILY);
}
/**
* Deserialize Knowledge from input stream.
*
* @param stream
* input stream
* @return deserialized Knowledge
* @throws IndicatorsException
* error while loading knowledge
*/
private static Knowledge load(final InputStream stream) throws IndicatorsException {
try {
final Knowledge knowledge = (Knowledge) XMLUtil.loadResource(stream,
CLASSES_FOR_JAXB);
knowledge.ecophysiologicalProcesses.forEach(ind ->
ind.setIndicatorCategory(IndicatorCategory.ECOPHYSIOLOGICAL_PROCESSES));
knowledge.indicators.forEach(ind ->
ind.setIndicatorCategory(IndicatorCategory.CLIMATIC_EFFECTS));
knowledge.culturalPractices.forEach(ind ->
ind.setIndicatorCategory(IndicatorCategory.CULTURAL_PRACTICES));
return knowledge;
} catch (final IndicatorsException ex) {
LOGGER.error(ex);
throw ex;
}
}
/**
* Deserialize Knowledge from embedded file.
*
* @param timescale timescale of indicators
* @return deserialized Knowledge
* @throws IndicatorsException error while loading knowledge
*/
public static Knowledge load(final TimeScale timescale) throws IndicatorsException {
final InputStream stream = Knowledge.class.getResourceAsStream(RESOURCES.get(timescale));
return load(stream);
}
/**
* Indicators for cultural practices.
*/
@XmlElementWrapper(name = "culturalPractices")
@XmlElement(name = "culturalPractice")
@Setter
private List<CompositeIndicator> culturalPractices;
/**
* Indicators for ecophysiological processes.
*/
@XmlElementWrapper(name = "ecophysiologicalProcesses")
@XmlElement(name = "ecophysiologicalProcess")
@Setter
private List<CompositeIndicator> ecophysiologicalProcesses;
/**
* Parameters for indicator and indicator criteria.
*/
@XmlElementWrapper(name = "parameters")
@XmlElement(name = "parameter")
@Getter
@Setter
private List<Parameter> parameters;
/**
* Timescale of indicators.
*/
@Getter
@Setter
@XmlAttribute
private TimeScale timescale = TimeScale.DAILY;
/**
* Notes library for indicators.
*/
@XmlElementWrapper(name = "notes")
@XmlElement(name = "note")
@Getter
@Setter
private List<Note> notes;
/**
* Measurement units for indicators.
*/
@XmlElementWrapper(name = "units")
@XmlElement(name = "unit")
@Getter
@Setter
private List<Unit> units;
/**
* Indicators for climatic effets.
*/
@XmlElementWrapper(name = "climaticEffects")
@XmlElement(name = "climaticEffect")
@Getter
@Setter
private List<CompositeIndicator> indicators;
/**
* Constructor.
*/
public Knowledge() {
culturalPractices = new ArrayList<>();
ecophysiologicalProcesses = new ArrayList<>();
indicators = new ArrayList<>();
}
@Override
public Knowledge clone() {
final Knowledge clone = new Knowledge();
culturalPractices.forEach(practice -> clone.culturalPractices.add(practice.clone()));
ecophysiologicalProcesses.forEach(process -> clone.ecophysiologicalProcesses.add(process.clone()));
indicators.forEach(clone.indicators::add);
return clone;
}
/**
* @param indicatorId indicator id
* @return indicator in climatic effects category
*/
private Indicator getClimaticEffectsIndicator(final String indicatorId) {
return getIndicator(indicatorId, indicators);
}
/**
* @return indicators in cultural practices category
*/
public List<CompositeIndicator> getCulturalPractices() {
return culturalPractices;
}
/**
* @return indicators in ecophysiological processes category
*/
public List<CompositeIndicator> getEcophysiologicalProcesses() {
return ecophysiologicalProcesses;
}
/**
* Loop on indicator in knowledge to get the Indicator defined its id.
*
* @param indicatorId indicator id
* @return indicator matching id or null
*/
public Indicator getIndicator(@NonNull final String indicatorId) {
Indicator result;
for (final CompositeIndicator indicator : indicators) {
if (indicatorId.equals(indicator.getId())) {
return indicator;
}
result = getIndicator(indicatorId, indicator.getIndicators());
if (result != null) {
result.setParent(indicator);
return result;
}
}
for (final CompositeIndicator indicator : culturalPractices) {
if (indicatorId.equals(indicator.getId())) {
return indicator;
}
result = getIndicator(indicatorId, indicator.getIndicators());
if (result != null) {
result.setParent(indicator);
return result;
}
}
for (final CompositeIndicator indicator : ecophysiologicalProcesses) {
if (indicatorId.equals(indicator.getId())) {
return indicator;
}
result = getIndicator(indicatorId, indicator.getIndicators());
if (result != null) {
result.setParent(indicator);
return result;
}
}
return null;
}
/**
* Get child indicators for the CLIMATIC indicator or the category.
*
* @param ind indicator with sub indicators
* @return child indicators for the category
*/
public List<? extends Indicator> getNextIndicators(
final CompositeIndicator ind) {
final IndicatorCategory category = ind.getIndicatorCategory();
final String id = ind.getId();
if (category == null) {
throw new IllegalArgumentException("No indicator category for "
+ id);
}
switch (category) {
case PHENO_PHASES:
return getEcophysiologicalProcesses();
case CULTURAL_PRACTICES:
if (ind.isPhase()) {
return getCulturalPractices();
}
return getIndicators();
case ECOPHYSIOLOGICAL_PROCESSES:
if (ind.isPhase()) {
return getEcophysiologicalProcesses();
}
return getIndicators();
case CLIMATIC_EFFECTS:
return ((CompositeIndicator) getClimaticEffectsIndicator(id))
.getIndicators();
default:
LOGGER.fatal("Not handled category: " + category);
throw new IllegalArgumentException("Not handled category: "
+ category);
}
}
/**
* Get the first parameter matching id.
*
* @param id parameter id
* @return parameter or null if not found
*/
public Parameter getParameterById(final String id) {
return this.parameters.stream()
.filter(parameter -> parameter.getId().equals(id))
.findFirst().orElse(null);
}
/**
* Update localized description and name of indicator from Knowledge.
*
* @param indicator indicator to update
*/
public void setI18n(final Indicator indicator) {
if (indicator.getId() == null) {
throw new IllegalArgumentException("indicator.id must not be null! "
+ indicator.getId());
}
final Indicator ind = getIndicator(indicator.getId());
if (ind != null) {
if (indicator.getNames() == null) {
indicator.setNames(new ArrayList<>());
} else {
indicator.getNames().clear();
}
if (ind.getNames() != null) {
indicator.getNames().addAll(ind.getNames());
}
if (indicator.getDescriptions() == null) {
indicator.setDescriptions(new ArrayList<>());
} else {
indicator.getDescriptions().clear();
}
if (ind.getDescriptions() != null) {
indicator.getDescriptions().addAll(ind.getDescriptions());
}
}
if (indicator instanceof CompositeIndicator) {
((CompositeIndicator) indicator).getIndicators().forEach(this::setI18n);
}
}
}