1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package fr.inrae.agroclim.indicators.model.data.phenology;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import org.apache.logging.log4j.Level;
30
31 import com.fasterxml.jackson.databind.MappingIterator;
32 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
33 import com.fasterxml.jackson.dataformat.csv.CsvParser;
34 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
35
36 import fr.inrae.agroclim.indicators.model.TimeScale;
37 import fr.inrae.agroclim.indicators.model.data.DataLoadingListener;
38 import fr.inrae.agroclim.indicators.model.data.FileLoader;
39 import fr.inrae.agroclim.indicators.model.data.Resource;
40 import fr.inrae.agroclim.indicators.model.data.ResourcesLoader;
41 import fr.inrae.agroclim.indicators.model.data.Variable;
42 import jakarta.xml.bind.annotation.XmlAccessType;
43 import jakarta.xml.bind.annotation.XmlAccessorType;
44 import jakarta.xml.bind.annotation.XmlElement;
45 import jakarta.xml.bind.annotation.XmlTransient;
46 import jakarta.xml.bind.annotation.XmlType;
47 import lombok.EqualsAndHashCode;
48 import lombok.Getter;
49 import lombok.Setter;
50 import lombok.extern.log4j.Log4j2;
51
52
53
54
55
56
57
58
59
60 @XmlAccessorType(XmlAccessType.FIELD)
61 @XmlType(propOrder = {"separator", "headers"})
62 @EqualsAndHashCode(
63 callSuper = true,
64 of = {"headers", "separator"}
65 )
66 @Log4j2
67 public final class PhenologyFileLoader extends FileLoader implements ResourcesLoader<List<AnnualStageData>> {
68
69
70
71
72 private static final long serialVersionUID = -8613079345988870000L;
73
74
75
76 public static final String YEAR_COLUMN = "year";
77
78
79
80
81 @Getter
82 @XmlElement(name = "header")
83 private String[] headers;
84
85
86
87
88 @Getter
89 @Setter
90 @XmlElement
91 private String separator = Resource.DEFAULT_SEP;
92
93
94
95
96 @XmlTransient
97 private int yearHeader = 0;
98
99
100
101
102 public PhenologyFileLoader() {
103 setDataFile(DataLoadingListener.DataFile.PHENOLOGICAL);
104 }
105
106
107
108
109
110
111
112
113
114
115
116 public PhenologyFileLoader(final String csvFile, final String[] csvHeaders,
117 final String csvSeparator) {
118 this();
119 setPath(csvFile);
120 setHeaders(csvHeaders);
121 this.separator = csvSeparator;
122 }
123
124 @Override
125 public PhenologyFileLoader clone() {
126 final PhenologyFileLoader clone = new PhenologyFileLoader();
127 clone.setPath(getPath());
128 clone.setHeaders(headers);
129 clone.separator = separator;
130 return clone;
131 }
132
133
134
135
136
137 public String getAbsolutePath() {
138 if (getFile() == null) {
139 throw new RuntimeException("PhenologyFileLoader.file is null!");
140 }
141 return getFile().getAbsolutePath();
142 }
143
144 @Override
145 public Map<String, String> getConfigurationErrors() {
146 final Map<String, String> errors = new HashMap<>();
147 if (getFile() == null) {
148 errors.put("phenology.file", "error.evaluation.phenology.file.missing");
149 } else if (!getFile().exists()) {
150 errors.put("phenology.file", "error.evaluation.phenology.file.doesnotexist");
151 } else if (getFile().length() == 0) {
152 errors.put("phenology.file", "error.evaluation.phenology.file.empty");
153 }
154 if (separator == null) {
155 errors.put("phenology.separator", "error.evaluation.phenology.separator.missing");
156 } else if (separator.isEmpty()) {
157 errors.put("phenology.separator", "error.evaluation.phenology.separator.empty");
158 }
159 if (headers == null) {
160 errors.put("phenology.header", "error.evaluation.phenology.header.missing");
161 }
162 if (errors.isEmpty()) {
163 return null;
164 }
165 return errors;
166 }
167
168 @Override
169 public Collection<String> getMissingVariables() {
170 throw new RuntimeException("Not implemented for phenology!");
171 }
172
173 @Override
174 public Set<Variable> getVariables() {
175 return new HashSet<>();
176 }
177
178 @Override
179 public List<AnnualStageData> load() {
180 final List<AnnualStageData> data = new ArrayList<>();
181 try {
182 final CsvSchema schema = CsvSchema.emptySchema()
183 .withSkipFirstDataRow(true)
184 .withColumnSeparator(separator.charAt(0));
185 final CsvMapper mapper = new CsvMapper();
186
187 mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);
188 final File csvFile = getFile();
189 final MappingIterator<Integer[]> it = mapper.readerFor(Integer[].class)
190 .with(schema).readValues(csvFile);
191 while (it.hasNext()) {
192 final Integer[] row = it.next();
193 final int lineNumber = it.getCurrentLocation().getLineNr();
194 final Integer year = row[yearHeader];
195 final AnnualStageData annualStageData = new AnnualStageData();
196 annualStageData.setYear(year);
197 for (int i = 0; i < row.length; i++) {
198 if (i != yearHeader && i < headers.length) {
199 annualStageData.add(headers[i], row[i]);
200 }
201 }
202 annualStageData.check(lineNumber, csvFile.getName());
203 fireDataLoadingAddEvent(annualStageData);
204 data.add(annualStageData);
205 }
206 } catch (final IOException e) {
207 LOGGER.catching(Level.ERROR, e);
208 }
209 return data;
210 }
211
212
213
214
215 public void setHeaders(final String[] csvHeaders) {
216 this.headers = csvHeaders;
217 for (int i = 0; i != headers.length - 1; i++) {
218 if (headers[i].equals("year")) {
219 this.yearHeader = i;
220 }
221 }
222 }
223
224 @Override
225 public void setTimeScale(final TimeScale timeScale) {
226
227 }
228 }