SimpleCriteria.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.criteria;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import fr.inrae.agroclim.indicators.exception.IndicatorsException;
import fr.inrae.agroclim.indicators.model.Parameter;
import fr.inrae.agroclim.indicators.model.data.DailyData;
import fr.inrae.agroclim.indicators.model.data.Variable;
import fr.inrae.agroclim.indicators.model.data.climate.ClimaticDailyData;
import fr.inrae.agroclim.indicators.util.Doublet;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;

/**
 * Criteria to compare value with threshold or to define variable to use in
 * aggregation indicator (eg.: Sum).
 *
 * Last changed : $Date$
 *
 * @author jcufi
 * @author $Author$
 * @version $Revision$
 */
@XmlRootElement
@XmlType(propOrder = {"inferiorToThreshold", "strict", "operator", "threshold", "noThreshold"})
@ToString
@Log4j2
@EqualsAndHashCode(callSuper = true, of = {"noThreshold", "operator", "threshold"})
public final class SimpleCriteria extends VariableCriteria {
    /**
     * UUID for Serializable.
     */
    private static final long serialVersionUID = -4284278102762199667L;

    /**
     * Tag name.
     */
    private static final String THRESHOLD = "threshold";

    /**
     * true for comparison inferior or inferior or equal, false for superior or superior or equal.
     */
    @XmlTransient
    private boolean inferiorToThreshold = true;

    /**
     * No threshold, to simply use the value (eg.: in sum).
     */
    @XmlElement
    @Getter
    @Setter
    private boolean noThreshold = false;

    /**
     * The relational operator to compare variable and threshold.
     */
    @XmlElement
    @Getter
    @Setter
    private RelationalOperator operator = RelationalOperator.LT;

    /**
     * Comparison is strictly inferior or superior.
     */
    @XmlTransient
    private boolean strict = true;

    /**
     * Threshold value.
     */
    @XmlElement
    @Getter
    private double threshold;

    /**
     * Criteria.
     */
    public SimpleCriteria() {
        super();
    }

    /**
     * Constructor.
     *
     * @param variable
     *            variable name
     * @param op
     *            operator for comparison
     * @param thr
     *            threshold value
     */
    public SimpleCriteria(final String variable, final RelationalOperator op, final double thr) {
        setOperator(op);
        setVariable(Variable.getByName(variable));
        setThreshold(thr);
    }

    @Override
    public SimpleCriteria clone() {
        final SimpleCriteria clone = new SimpleCriteria();
        clone.threshold = threshold;
        clone.setVariable(getVariable());
        clone.inferiorToThreshold = inferiorToThreshold;
        clone.strict = strict;
        clone.noThreshold = noThreshold;
        clone.operator = operator;
        if (getParameters() != null) {
            clone.setParameters(getParameters());
        }
        return clone;
    }

    @Override
    public boolean eval(final DailyData dailydata) throws IndicatorsException {
        if (dailydata == null) {
            return false;
        }
        if (isNoThreshold()) {
            return true;
        }
        final double val = getValueOf(dailydata);
        return operator.eval(val, getThreshold());
    }

    /**
     * @param data
     *            climatic data
     * @return formula
     * @throws IndicatorsException
     *             exception while getting data
     */
    public String getFormula(final ClimaticDailyData data) throws IndicatorsException {
        final StringBuilder sb = new StringBuilder();
        sb.append(getValueOf(data));
        sb.append(" ");
        sb.append(operator.getRepr());
        sb.append(" ");
        sb.append(getThreshold());
        return sb.toString();
    }

    @Override
    public List<Doublet<Parameter, Number>> getParameterDefaults() {
        if (getParameters() == null) {
            return List.of();
        }
        final Optional<Parameter> found = getParameters().stream() //
                .filter(a -> THRESHOLD.equals(a.getAttribute())) //
                .findFirst();
        if (found.isEmpty()) {
            return List.of();
        }
        return List.of(Doublet.of(found.get(), threshold));
    }

    @Override
    public Map<String, Double> getParametersValues() {
        final Map<String, Double> val = new HashMap<>();
        // if no substitution is defined
        if (getParameters() == null) {
            return val;
        }
        for (final Parameter param : getParameters()) {
            if (Objects.equals(param.getAttribute(), THRESHOLD)) {
                val.put(param.getId(), threshold);
            }
        }
        return val;
    }

    /**
     * @deprecated use operator
     * @return always null for migration
     */
    @Deprecated(forRemoval = true)
    public Boolean isInferiorToThreshold() {
        return null;
    }

    /**
     * @deprecated use operator
     * @return always null for migration.
     */
    @Deprecated(forRemoval = true)
    public Boolean isStrict() {
        return null;
    }

    @Override
    public void removeParameter(final Parameter param) {
        if (this.getParameters() != null) {
            this.getParameters().remove(param);
        }
    }

    /**
     * @param value true for comparison inferior or inferior or equal, false for superior or superior or equal.
     */
    @XmlElement
    public void setInferiorToThreshold(final Boolean value) {
        this.inferiorToThreshold = value;
        setOperator();
    }

    /**
     * Set operator according to inferiorToThreshold and strict.
     */
    private void setOperator() {
        if (this.inferiorToThreshold) {
            if (strict) {
                operator = RelationalOperator.LT;
            } else {
                operator = RelationalOperator.LE;
            }
        } else {
            if (strict) {
                operator = RelationalOperator.GT;
            } else {
                operator = RelationalOperator.GE;
            }
        }
    }

    @Override
    public void setParametersValues(final Map<String, Double> values) {
        // if no substitution is defined
        if (getParameters() == null || values == null || values.isEmpty()) {
            return;
        }
        for (final Parameter param : getParameters()) {
            if (Objects.equals(param.getAttribute(), THRESHOLD)) {
                final String id = param.getId();
                if (values.containsKey(id) && values.get(id) != null) {
                    if (values.get(id) == null) {
                        LOGGER.error("Strange, value of parameter {} is null!", id);
                    } else {
                        setThreshold(values.get(id));
                    }
                }
            } else {
                LOGGER.error("Attribute {} not handled!", param.getAttribute());
            }
        }
    }

    /**
     * @param value Comparison is strictly inferior or superior.
     */
    @XmlElement
    public void setStrict(final Boolean value) {
        this.strict = value;
        setOperator();
    }

    /**
     * @param value Threshold value.
     */
    public void setThreshold(final double value) {
        final double old = threshold;
        threshold = value;
        getPropertySupport().firePropertyChange(THRESHOLD, old, value);
    }

    @Override
    public String toStringTree(final String ident) {
        final StringBuilder sb = new StringBuilder();
        sb.append(ident).append("  class: ").append(getClass().getName())
        .append("\n");
        sb.append(ident).append("  operator: ")
        .append(operator.getRepr()).append("\n");
        sb.append(ident).append("  noThreshold: ").append(noThreshold)
        .append("\n");
        sb.append(ident).append("  threshold: ").append(threshold)
        .append("\n");
        sb.append(ident).append("  variable: ").append(getVariable())
        .append("\n");
        return sb.toString();
    }
}