AggregationIndicator.java

package fr.inrae.agroclim.indicators.model.indicator;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.DoubleStream;

import fr.inrae.agroclim.indicators.exception.IndicatorsException;
import fr.inrae.agroclim.indicators.exception.ThrowingToDoubleFunction;
import fr.inrae.agroclim.indicators.exception.type.ComputationErrorType;
import fr.inrae.agroclim.indicators.model.criteria.Criteria;
import fr.inrae.agroclim.indicators.model.criteria.NoCriteria;
import fr.inrae.agroclim.indicators.model.criteria.SimpleCriteria;
import fr.inrae.agroclim.indicators.model.criteria.VariableCriteria;
import fr.inrae.agroclim.indicators.model.data.DailyData;
import fr.inrae.agroclim.indicators.model.data.Resource;
import fr.inrae.agroclim.indicators.model.data.Variable;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

/**
 * Aggregate value defined in a {@link SimpleCriteria} using a {@link Function}
 * on {@link DoubleStream} created for {@link AggregationIndicator#variable}.
 *
 * Last changed : $Date: 2023-06-16 10:36:46 +0200 (ven., 16 juin 2023) $
 *
 * @author omaury
 */
@EqualsAndHashCode(callSuper = true)
public abstract class AggregationIndicator extends SimpleIndicatorWithCriteria implements Detailable {

    /**
     * UUID for Serialize.
     */
    private static final long serialVersionUID = 6030595237342422021L;

    /**
     * Variable to aggregate.
     */
    @XmlElement(name = "variable")
    @Getter
    @Setter
    private Variable variable;

    /**
     * @param stream stream to aggregate
     * @return aggregated stream as single value.
     */
    protected abstract double aggregate(DoubleStream stream);

    /**
     * Override {@link SimpleIndicatorWithCriteria#clone()}.
     * @return clone
     * @throws CloneNotSupportedException should not occur
     */
    @Override
    public AggregationIndicator clone() throws CloneNotSupportedException {
        final AggregationIndicator clone = (AggregationIndicator) super.clone();
        clone.variable = this.variable;
        if (getCriteria() != null) {
            clone.setCriteria(getCriteria().clone());
        }
        return clone;
    }

    @Override
    public final double computeSingleValue(final Resource<? extends DailyData> res) throws IndicatorsException {
        final DoubleStream stream;
        try {
            if (getCriteria() instanceof final NoCriteria criteria) {
                Objects.requireNonNull(criteria.getVariable(),
                        getId() + ".getCriteria().getVariable() must not be null! " + getVariable());
                // syntax changed to hack Eclipse.
                stream = res.getData().stream() //
                        .mapToDouble(ThrowingToDoubleFunction.wrap(criteria::getValueOf));
            } else if (getCriteria() instanceof final SimpleCriteria criteria) {
                final Predicate<DailyData> predicate = d -> {
                    try {
                        return criteria.eval(d);
                    } catch (final IndicatorsException e) {
                        return false;
                    }
                };
                stream = res.getData().stream() //
                        .filter(predicate) //
                        .mapToDouble(ThrowingToDoubleFunction.wrap(criteria::getValueOf));
            } else {
                throw new IndicatorsException(ComputationErrorType.CRITERIA_ISNT_NOCRITERIA_SIMPLECRITERIA, getId());
            }
            return aggregate(stream);
        } catch (final RuntimeException ex) {
            if (ex.getCause() instanceof final IndicatorsException iex) {
                throw iex;
            }
            throw ex;
        }
    }

    @Override
    public final Criteria getCriteria() {
        if (super.getCriteria() == null) {
            final var criteria = new NoCriteria();
            criteria.setVariable(variable);
            super.setCriteria(criteria);
        }
        if (super.getCriteria() instanceof VariableCriteria && this.variable != null) {
            ((VariableCriteria) super.getCriteria()).setVariable(this.variable);
        }
        return super.getCriteria();
    }

    /**
     * To override, use super or use toStringTree(indent) and variable.
     *
     * @param indent intent of tree representation
     * @return tree representation
     */
    @Override
    public String toStringTree(final String indent) {
        final StringBuilder sb = new StringBuilder(super.toStringTree(indent));
        sb.append(indent).append("  variable: ").append(variable).append("\n");
        return sb.toString();
    }
}