View Javadoc
1   /**
2    * This file is part of Indicators.
3    *
4    * Indicators is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * Indicators is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
16   */
17  package fr.inrae.agroclim.indicators.model.data;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.EnumMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Set;
28  
29  import fr.inrae.agroclim.indicators.exception.ErrorMessage;
30  import fr.inrae.agroclim.indicators.exception.type.ResourceErrorType;
31  import fr.inrae.agroclim.indicators.model.TimeScale;
32  import fr.inrae.agroclim.indicators.model.data.Variable.Type;
33  import fr.inrae.agroclim.indicators.model.data.climate.ClimaticResource;
34  import fr.inrae.agroclim.indicators.model.data.phenology.PhenologicalResource;
35  import fr.inrae.agroclim.indicators.util.DateUtils;
36  import lombok.Getter;
37  import lombok.NonNull;
38  import lombok.Setter;
39  import lombok.extern.log4j.Log4j2;
40  
41  /**
42   * The resource manager holds all climate, phenology and soilResource data to
43   * compute indicators.
44   *
45   * Its responsibility is data storage and checking data consistency.
46   *
47   * Last changed : $Date: 2023-03-16 17:36:45 +0100 (jeu. 16 mars 2023) $
48   *
49   * @author $Author: omaury $
50   * @version $Revision: 644 $
51   */
52  @Log4j2
53  public final class ResourceManager implements Serializable, Cloneable {
54  
55      /**
56       * UUID for Serializable.
57       */
58      private static final long serialVersionUID = 5645729254868967485L;
59      /**
60       * @param errors errors into add message
61       * @param errorI18nKey Message key from .property resource.
62       */
63      private static void addErrorMessage(
64              final Map<ResourceErrorType, ErrorMessage> errors,
65              final ResourceErrorType errorI18nKey) {
66          errors.put(errorI18nKey.getParent(), new ErrorMessage(
67                  "fr.inrae.agroclim.indicators.resources.messages",
68                  errorI18nKey, null));
69      }
70  
71      /**
72       * Storage of climatic daily data.
73       */
74      @Getter
75      private final ClimaticResource climaticResource;
76  
77      /**
78       * Number of development years for the crop.
79       */
80      @Setter
81      @NonNull
82      private Integer cropDevelopmentYears = 1;
83  
84      /**
85       * Storage of phenological daily data.
86       */
87      @Getter
88      private final PhenologicalResource phenologicalResource;
89  
90      /**
91       * Time scale of evaluation.
92       */
93      @Setter
94      private TimeScale timeScale;
95  
96      /**
97       * Variable used to compute indicator.
98       */
99      @Setter
100     private Set<Variable> variables;
101 
102     /**
103      * Constructor.
104      */
105     public ResourceManager() {
106         climaticResource = new ClimaticResource();
107         phenologicalResource = new PhenologicalResource();
108     }
109 
110     /**
111      * Constructor for cloning.
112      *
113      * @param climatic
114      *            climatic resource
115      * @param phenological
116      *            phenological resource
117      */
118     public ResourceManager(final ClimaticResource climatic,
119             final PhenologicalResource phenological) {
120         climaticResource = climatic;
121         phenologicalResource = phenological;
122     }
123 
124     @Override
125     public ResourceManager clone() throws CloneNotSupportedException {
126         return new ResourceManager(climaticResource.clone(), phenologicalResource.clone());
127     }
128 
129     /**
130      * @return consistency errors
131      */
132     public Map<ResourceErrorType, ErrorMessage> getConsitencyErrors() {
133         final Map<ResourceErrorType, ErrorMessage> errors = new EnumMap<>(ResourceErrorType.class);
134         // variables not set ?
135         if (variables == null) {
136             addErrorMessage(errors, ResourceErrorType.VARIABLES_MISSING);
137             return errors;
138         }
139         if (variables.isEmpty()) {
140             addErrorMessage(errors, ResourceErrorType.VARIABLES_EMPTY);
141             return errors;
142         }
143         // empty climate
144         final List<Integer> climaticYears = climaticResource.getYears();
145         if (hasClimaticVariables() && climaticResource.getData().isEmpty()) {
146             addErrorMessage(errors, ResourceErrorType.CLIMATE_EMPTY);
147         } else {
148             // missing days or hours
149             final int nbClimatic = climaticResource.getData().size();
150             int nb = 0;
151             nb = climaticYears.stream()
152                     .map(DateUtils::nbOfDays)
153                     .reduce(nb, Integer::sum);
154             if (timeScale == TimeScale.HOURLY) {
155                 nb = nb * DateUtils.NB_OF_HOURS_IN_DAY;
156             }
157             if (nbClimatic != nb) {
158                 addErrorMessage(errors, ResourceErrorType.CLIMATE_SIZE_WRONG);
159             }
160         }
161         // empty phenology
162         if (phenologicalResource.isEmpty()) {
163             addErrorMessage(errors, ResourceErrorType.PHENO_EMPTY);
164         }
165         if (!errors.isEmpty()) {
166             return errors;
167         }
168         // period
169         final List<Integer> phenoYears = phenologicalResource.getYears();
170         if (hasClimaticVariables() && climaticYears.isEmpty()) {
171             addErrorMessage(errors, ResourceErrorType.CLIMATE_YEARS_EMPTY);
172         }
173         if (phenoYears.isEmpty()) {
174             addErrorMessage(errors, ResourceErrorType.PHENO_YEARS_EMPTY);
175         }
176         if (!errors.isEmpty()) {
177             return errors;
178         }
179         if (cropDevelopmentYears == null) {
180             addErrorMessage(errors, ResourceErrorType.RESOURCES_CROPDEVELOPMENT_YEARS);
181             return errors;
182         }
183         // Phenology data drives evaluation, so
184         // - it's allowed to have more climate years that pheno years
185         // - each pheno year must match a climate year
186         final List<Integer> expectedClimateYears = new ArrayList<>(phenoYears);
187         Collections.sort(expectedClimateYears);
188         for (int i = 1; i < cropDevelopmentYears; i++) {
189             LOGGER.trace("First year {} will not have phenological stages.", expectedClimateYears.get(0));
190             expectedClimateYears.remove(0);
191         }
192         if (hasClimaticVariables()
193                 && !climaticYears.containsAll(expectedClimateYears)) {
194             final Collection<Serializable> theMissing = new ArrayList<>(expectedClimateYears);
195             theMissing.removeAll(climaticYears);
196             final ErrorMessage error = new ErrorMessage(
197                     "fr.inrae.agroclim.indicators.resources.messages",
198                     ResourceErrorType.CLIMATE_YEARS_MISSING,
199                     theMissing);
200             errors.put(ResourceErrorType.CLIMATE_YEARS_MISSING.getParent(), error);
201         }
202         if (!errors.isEmpty()) {
203             return errors;
204         }
205         // number of data (days)
206         if (hasClimaticVariables() && hasSoilVariables()) {
207             int nbDays = 0;
208             for (final int year : climaticYears) {
209                 nbDays += DateUtils.nbOfDays(year);
210             }
211             final int nbSoil = climaticResource.getData().size();
212             if (nbSoil != nbDays) {
213                 addErrorMessage(errors, ResourceErrorType.SOIL_SIZE_WRONG);
214             }
215         }
216         if (errors.isEmpty()) {
217             return null;
218         } else {
219             return errors;
220         }
221     }
222 
223     /**
224      * @return at least one of the variables used to compute indicator is a
225      *         climatic variable
226      */
227     public boolean hasClimaticVariables() {
228         Objects.requireNonNull(variables, "variables is not set!");
229         return variables.stream().anyMatch(variable -> variable != null && Type.CLIMATIC.equals(variable.getType()));
230     }
231 
232     /**
233      * @return at least one of the variables used to compute indicator is a soil
234      *         variable
235      */
236     public boolean hasSoilVariables() {
237         Objects.requireNonNull(variables, "variables is not set!");
238         return variables.stream().anyMatch(variable -> variable != null && Type.SOIL.equals(variable.getType()));
239     }
240 
241 }