EvaluationSettings.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.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

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.data.Variable;
import fr.inrae.agroclim.indicators.model.data.climate.ClimateFileLoader;
import fr.inrae.agroclim.indicators.model.data.climate.ClimateLoaderProxy;
import fr.inrae.agroclim.indicators.model.data.phenology.PhenologyCalculator;
import fr.inrae.agroclim.indicators.model.data.phenology.PhenologyFileLoader;
import fr.inrae.agroclim.indicators.model.data.phenology.PhenologyLoaderProxy;
import fr.inrae.agroclim.indicators.model.data.soil.SoilLoaderProxy;
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.MultiLinear;
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.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.PhaseLength;
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 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 jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;

/**
 * Settings from XML.
 *
 * Last change $Date$
 *
 * @author $Author$
 * @version $Revision$
 */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"climateLoader", "notes", "phenologyLoader", "soilLoader",
        "soilPhenologyCalculator", "name", "type", "timescale", "evaluation"})
@EqualsAndHashCode(
        callSuper = false,
        of = {"climateLoader", "evaluation", "filePath", "knowledge", "name",
                "phenologyLoader", "soilLoader", "soilPhenologyCalculator"})
@Log4j2
@ToString
public final class EvaluationSettings implements Cloneable, Serializable {

    /**
     * Classes needed to load evaluation-settings.xml with XMLUtil.
     */
    public static final Class<?>[] CLASSES_FOR_JAXB = new Class<?>[] {
        EvaluationSettings.class, EvaluationType.class, Knowledge.class, Note.class,
        PhaseLength.class, Variable.class, TimeScale.class,
        /*- Climate */
        ClimateLoaderProxy.class, ClimateFileLoader.class,
        /*- Phenology */
        PhenologyLoaderProxy.class, PhenologyFileLoader.class,
        /*- Soil */
        /*- Indicators */
        Average.class, AverageOfDiff.class, CompositeIndicator.class,
        DayOfYear.class, DiffOfSum.class, Formula.class, Indicator.class, Max.class, MaxWaveLength.class, Min.class,
        Normal.class, NumberOfDays.class, NumberOfWaves.class,
        PotentialSowingDaysFrequency.class, Quotient.class,
        SimpleIndicator.class, Sum.class, Tamm.class,
        /*- Criteria */
        ComparisonCriteria.class, CompositeCriteria.class, ExpressionParameter.class, FormulaCriteria.class,
        LogicalOperator.class, NoCriteria.class, Parameter.class, RelationalOperator.class, SimpleCriteria.class,
        /*- Normalisation */
        AggregationFunction.class, Exponential.class, JEXLFunction.class,
        Linear.class, MultiLinear.class, NormalizationFunction.class, Sigmoid.class
    };

    /**
     * UUID for Serializable.
     */
    private static final long serialVersionUID = 3308126437081517192L;

    /**
     * Climate.
     */
    @XmlElement(name = "climate")
    @Getter
    private ClimateLoaderProxy climateLoader;

    /**
     * The composite indicator.
     */
    @XmlElement(name = "compositeIndicator")
    @Getter
    @Setter
    private CompositeIndicator evaluation;

    /**
     * evaluation-settings.xml file path.
     */
    @XmlTransient
    @Getter
    private String filePath;

    /**
     * Knowledge XML.
     */
    @Getter
    private transient Knowledge knowledge;

    /**
     * Evaluation name.
     */
    @XmlElement
    @Getter
    @Setter
    private String name;

    /**
     * Phenology definition.
     */
    @XmlElement(name = "phenology")
    @Getter
    @Setter
    private PhenologyLoaderProxy phenologyLoader;

    /**
     * The proxy with decides which loader to use.
     */
    @XmlElement(name = "soil")
    @Getter
    @Setter
    private SoilLoaderProxy soilLoader;

    /**
     * The phenology calculator with variety parameters add values to compute 4
     * stages for soil calculator.
     */
    @XmlElement(name = "soilPhenologyCalculator")
    @Getter
    @Setter
    private PhenologyCalculator soilPhenologyCalculator;

    /**
     * Timescale of indicators.
     */
    @Getter
    @Setter
    @XmlAttribute
    private TimeScale timescale = TimeScale.DAILY;

    /**
     * Date of last modification.
     */
    @Getter
    @Setter
    @XmlAttribute(name = "timestamp", required = false)
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime timestamp;

    /**
     * Evaluation type.
     */
    @Getter
    @Setter
    @XmlElement(name = "type")
    private EvaluationType type = EvaluationType.WITH_AGGREGATION;

    /**
     * Version of Indicators.
     */
    @Getter
    @Setter
    @XmlAttribute(required = false)
    private String version = "";
    /**
     * Notes library for indicators.
     */
    @XmlElementWrapper(name = "notes")
    @XmlElement(name = "note")
    @Getter
    @Setter
    private List<Note> notes;

    /**
     * Constructor.
     */
    public EvaluationSettings() {
        evaluation = new Evaluation();
    }

    @Override
    public EvaluationSettings clone() throws CloneNotSupportedException {
        final EvaluationSettings clone = new EvaluationSettings();
        if (climateLoader != null) {
            clone.climateLoader = climateLoader.clone();
        }
        if (evaluation != null) {
            clone.evaluation = evaluation.clone();
        }
        clone.filePath = filePath;
        if (knowledge != null) {
            clone.knowledge = knowledge.clone();
        }
        clone.name = name;
        if (phenologyLoader != null) {
            clone.phenologyLoader = phenologyLoader.clone();
        }
        if (soilLoader != null) {
            clone.soilLoader = soilLoader.clone();
        }
        return clone;
    }

    /**
     * @return errors of configuration.
     */
    public Map<String, String> getConfigurationErrors() {
        final Map<String, String> errors = new HashMap<>();
        if (getClimateLoader() == null) {
            errors.put("evaluationSettings.climate",
                    "error.evaluation.climate.null");
        } else if (getClimateLoader().getConfigurationErrors() != null) {
            errors.putAll(getClimateLoader().getConfigurationErrors());
        }
        if (getSoilLoader() != null
                && getSoilLoader().getConfigurationErrors() != null) {
            errors.putAll(getSoilLoader().getConfigurationErrors());
        }
        if (getPhenologyLoader() == null) {
            errors.put("evaluationSettings.phenology", "error.evaluation.phenology.null");
        } else if (getPhenologyLoader().getConfigurationErrors() != null) {
            errors.putAll(getPhenologyLoader().getConfigurationErrors());
        }
        return errors;
    }

    /**
     * Load Knowledge.
     * @throws IndicatorsException while loading Knowledge
     */
    public void initializeKnowledge() throws IndicatorsException {
        knowledge = Knowledge.load(timescale);
    }

    /**
     * @param value climate resource
     */
    public void setClimate(final ClimateLoaderProxy value) {
        this.climateLoader = value;
    }

    /**
     * Set evaluation-settings.xml file path.
     *
     * @param path evaluation-settings.xml file path.
     */
    public void setFilePath(final String path) {
        if (!Objects.equals(filePath, path)) {
            this.filePath = path;
            final Path baseDir = Paths.get(path).getParent();
            if (climateLoader != null && climateLoader.getFile() != null) {
                climateLoader.getFile().setBaseDirectory(baseDir);
            }
            if (phenologyLoader != null && phenologyLoader.getFile() != null) {
                phenologyLoader.getFile().setBaseDirectory(baseDir);
            }
        }
    }
}