1 /*
2 * Copyright 2016 Andrew Rucker Jones.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.opencsv.bean;
17
18 import com.opencsv.ICSVParser;
19 import com.opencsv.bean.processor.PreAssignmentProcessor;
20 import com.opencsv.bean.processor.StringProcessor;
21 import com.opencsv.bean.validators.PreAssignmentValidator;
22 import com.opencsv.bean.validators.StringValidator;
23 import com.opencsv.exceptions.*;
24 import org.apache.commons.lang3.ObjectUtils;
25 import org.apache.commons.lang3.StringUtils;
26
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.util.Locale;
30 import java.util.Objects;
31 import java.util.ResourceBundle;
32
33 /**
34 * This base bean takes over the responsibility of converting the supplied
35 * string to the proper type for the destination field and setting the
36 * destination field.
37 * <p>All custom converters must be descended from this class.</p>
38 * <p>Internally, opencsv uses another set of classes for the actual conversion,
39 * leaving this class mostly to deal with assigment to bean fields.</p>
40 *
41 * @param <T> Type of the bean being populated
42 * @param <I> Type of the index into a multivalued field
43 * @author Andrew Rucker Jones
44 * @since 3.8
45 */
46 abstract public class AbstractBeanField<T, I> implements BeanField<T, I> {
47
48 /**
49 * The type the field is located in.
50 * This is not necessarily the declaring class in the case of inheritance,
51 * but rather the type that opencsv expects to instantiate.
52 */
53 protected Class<?> type;
54
55 /**
56 * The field this class represents.
57 */
58 protected Field field;
59
60 /**
61 * Whether or not this field is required.
62 */
63 protected boolean required;
64
65 /**
66 * Locale for error messages.
67 */
68 protected Locale errorLocale;
69
70 /**
71 * A class that converts from a string to the destination type on reading
72 * and vice versa on writing.
73 * This is only used for opencsv-internal conversions, not by custom
74 * converters.
75 */
76 protected CsvConverter converter;
77
78 /**
79 * An encapsulated way of accessing the member variable associated with this
80 * field.
81 */
82 protected FieldAccess<Object> fieldAccess;
83
84 /**
85 * Default nullary constructor, so derived classes aren't forced to create
86 * a constructor identical to this one.
87 */
88 public AbstractBeanField() {
89 required = false;
90 errorLocale = Locale.getDefault();
91 }
92
93 /**
94 * @param type The type of the class in which this field is found. This is
95 * the type as instantiated by opencsv, and not necessarily the
96 * type in which the field is declared in the case of
97 * inheritance.
98 * @param field A {@link java.lang.reflect.Field} object.
99 * @param required Whether or not this field is required in input
100 * @param errorLocale The errorLocale to use for error messages.
101 * @param converter The converter to be used to perform the actual data
102 * conversion
103 * @since 4.2
104 */
105 public AbstractBeanField(Class<?> type, Field field, boolean required, Locale errorLocale, CsvConverter converter) {
106 this.type = type;
107 this.field = field;
108 this.required = required;
109 // Once we support Java 9, we can replace ObjectUtils.defaultIfNull() with Objects.requireNonNullElse()
110 this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
111 this.converter = converter;
112 fieldAccess = new FieldAccess<>(this.field);
113 }
114
115 @Override
116 public Class<?> getType() {
117 return type;
118 }
119
120 @Override
121 public void setType(Class<?> type) { this.type = type; }
122
123 @Override
124 public void setField(Field field) {
125 this.field = field;
126 fieldAccess = new FieldAccess<>(this.field);
127 }
128
129 @Override
130 public Field getField() {
131 return this.field;
132 }
133
134 @Override
135 public boolean isRequired() {
136 return required;
137 }
138
139 @Override
140 public void setRequired(boolean required) {
141 this.required = required;
142 }
143
144 @Override
145 public void setErrorLocale(Locale errorLocale) {
146 this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
147 if (converter != null) {
148 converter.setErrorLocale(this.errorLocale);
149 }
150 }
151
152 @Override
153 public Locale getErrorLocale() {
154 return this.errorLocale;
155 }
156
157 @Override
158 public final void setFieldValue(Object bean, String value, String header)
159 throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException,
160 CsvConstraintViolationException, CsvValidationException {
161 if (required && StringUtils.isBlank(value)) {
162 throw new CsvRequiredFieldEmptyException(
163 bean.getClass(), field,
164 String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("required.field.empty"),
165 field.getName()));
166 }
167
168 PreAssignmentProcessor[] processors = field.getAnnotationsByType(PreAssignmentProcessor.class);
169
170 String fieldValue = value;
171
172 for (PreAssignmentProcessor processor : processors) {
173 fieldValue = preProcessValue(processor, fieldValue);
174 }
175
176 PreAssignmentValidator[] validators = field.getAnnotationsByType(PreAssignmentValidator.class);
177
178 for (PreAssignmentValidator validator : validators) {
179 validateValue(validator, fieldValue);
180 }
181
182 assignValueToField(bean, convert(fieldValue), header);
183 }
184
185 private String preProcessValue(PreAssignmentProcessor processor, String value) throws CsvValidationException {
186 try {
187 StringProcessor stringProcessor = processor.processor().getDeclaredConstructor().newInstance();
188 stringProcessor.setParameterString(processor.paramString());
189 return stringProcessor.processString(value);
190 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
191 throw new CsvValidationException(String.format(
192 ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
193 .getString("validator.instantiation.impossible"),
194 processor.processor().getName(), field.getName()));
195 }
196 }
197
198 private void validateValue(PreAssignmentValidator validator, String value) throws CsvValidationException {
199 try {
200 StringValidator stringValidator = validator.validator().getDeclaredConstructor().newInstance();
201 stringValidator.setParameterString(validator.paramString());
202 stringValidator.validate(value, this);
203 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
204 throw new CsvValidationException(String.format(
205 ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
206 .getString("validator.instantiation.impossible"),
207 validator.validator().getName(), field.getName()));
208 }
209 }
210
211 @Override
212 public Object getFieldValue(Object bean) {
213 Object o = null;
214 try {
215 o = fieldAccess.getField(bean);
216 }
217 catch(IllegalAccessException | InvocationTargetException e) {
218 // Our testing indicates these exceptions probably can't be thrown,
219 // but they're declared, so we have to deal with them. It's an
220 // alibi catch block.
221 CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
222 bean, field,
223 String.format(ResourceBundle.getBundle(
224 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
225 .getString("error.introspecting.field"),
226 field.getName(), bean.getClass().toString()));
227 csve.initCause(e);
228 throw csve;
229 }
230 return o;
231 }
232
233 /**
234 * @return {@code value} wrapped in an array, since we assume most values
235 * will not be multi-valued
236 * @since 4.2
237 */
238 // The rest of the Javadoc is inherited
239 @Override
240 public Object[] indexAndSplitMultivaluedField(Object value, I index)
241 throws CsvDataTypeMismatchException {
242 return new Object[]{value};
243 }
244
245 /**
246 * Whether or not this implementation of {@link BeanField} considers the
247 * value passed in as empty for the purposes of determining whether or not
248 * a required field is empty.
249 * <p>This allows any overriding class to define "empty" while writing
250 * values to a CSV file in a way that is meaningful for its own data. A
251 * simple example is a {@link java.util.Collection} that is not null, but
252 * empty.</p>
253 * <p>The default implementation simply checks for {@code null}.</p>
254 *
255 * @param value The value of a field out of a bean that is being written to
256 * a CSV file. Can be {@code null}.
257 * @return Whether or not this implementation considers {@code value} to be
258 * empty for the purposes of its conversion
259 * @since 4.2
260 */
261 protected boolean isFieldEmptyForWrite(Object value) {
262 return value == null;
263 }
264
265 /**
266 * Assigns the given object to this field of the destination bean.
267 * <p>Uses the setter method if available.</p>
268 * <p>Derived classes can override this method if they have special needs
269 * for setting the value of a field, such as adding to an existing
270 * collection.</p>
271 *
272 * @param bean The bean in which the field is located
273 * @param obj The data to be assigned to this field of the destination bean
274 * @param header The header from the CSV file under which this value was found.
275 * @throws CsvDataTypeMismatchException If the data to be assigned cannot
276 * be converted to the type of the destination field
277 */
278 protected void assignValueToField(Object bean, Object obj, String header)
279 throws CsvDataTypeMismatchException {
280
281 // obj == null means that the source field was empty. Then we simply
282 // leave the field as it was initialized by the VM. For primitives,
283 // that will be values like 0, and for objects it will be null.
284 if (obj != null) {
285 try {
286 fieldAccess.setField(bean, obj);
287 } catch (InvocationTargetException | IllegalAccessException e) {
288 CsvBeanIntrospectionException csve =
289 new CsvBeanIntrospectionException(bean, field,
290 e.getLocalizedMessage());
291 csve.initCause(e);
292 throw csve;
293 } catch (IllegalArgumentException e2) {
294 CsvDataTypeMismatchException csve =
295 new CsvDataTypeMismatchException(obj, field.getType());
296 csve.initCause(e2);
297 throw csve;
298 }
299 }
300 }
301
302 /**
303 * Method for converting from a string to the proper datatype of the
304 * destination field.
305 * This method must be specified in all non-abstract derived classes.
306 *
307 * @param value The string from the selected field of the CSV file. If the
308 * field is marked as required in the annotation, this value is guaranteed
309 * not to be null, empty or blank according to
310 * {@link org.apache.commons.lang3.StringUtils#isBlank(java.lang.CharSequence)}
311 * @return An {@link java.lang.Object} representing the input data converted
312 * into the proper type
313 * @throws CsvDataTypeMismatchException If the input string cannot be converted into
314 * the proper type
315 * @throws CsvConstraintViolationException When the internal structure of
316 * data would be violated by the data in the CSV file
317 */
318 protected abstract Object convert(String value)
319 throws CsvDataTypeMismatchException, CsvConstraintViolationException;
320
321 /**
322 * This method takes the current value of the field in question in the bean
323 * passed in and converts it to a string.
324 * It is actually a stub that calls {@link #convertToWrite(java.lang.Object)}
325 * for the actual conversion, and itself performs validation and handles
326 * exceptions thrown by {@link #convertToWrite(java.lang.Object)}. The
327 * validation consists of verifying that both {@code bean} and {@link #field}
328 * are not null before calling {@link #convertToWrite(java.lang.Object)}.
329 */
330 // The rest of the Javadoc is automatically inherited
331 @Override
332 public final String[] write(Object bean, I index) throws CsvDataTypeMismatchException,
333 CsvRequiredFieldEmptyException {
334
335 // If the input is empty, check if the field is required
336 Object value = bean != null ? getFieldValue(bean): null;
337 if(required && (bean == null || isFieldEmptyForWrite(value))) {
338 throw new CsvRequiredFieldEmptyException(type, field,
339 String.format(ResourceBundle.getBundle(
340 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
341 .getString("required.field.empty"),
342 field.getName()));
343 }
344
345 String[] result;
346 Object[] multivalues = indexAndSplitMultivaluedField(value, index);
347 String[] intermediateResult = new String[multivalues.length];
348 try {
349 for (int i = 0; i < multivalues.length; i++) {
350 intermediateResult[i] = convertToWrite(multivalues[i]);
351 }
352 result = intermediateResult;
353 } catch (CsvDataTypeMismatchException e) {
354 CsvDataTypeMismatchException csve = new CsvDataTypeMismatchException(
355 bean, field.getType(), e.getMessage());
356 csve.initCause(e.getCause());
357 throw csve;
358 } catch (CsvRequiredFieldEmptyException e) {
359 // Our code no longer throws this exception from here, but
360 // rather from write() using isFieldEmptyForWrite() to determine
361 // when to throw the exception. But user code is still allowed
362 // to override convertToWrite() and throw this exception
363 Class<?> beanClass = bean == null ? null : bean.getClass();
364 CsvRequiredFieldEmptyException csve = new CsvRequiredFieldEmptyException(
365 beanClass, field, e.getMessage());
366 csve.initCause(e.getCause());
367 throw csve;
368 }
369 return result;
370 }
371
372 /**
373 * This is the method that actually performs the conversion from field to
374 * string for {@link #write(java.lang.Object, java.lang.Object) } and should
375 * be overridden in derived classes.
376 * <p>The default implementation simply calls {@code toString()} on the
377 * object in question. Derived classes will, in most cases, want to override
378 * this method. Alternatively, for complex types, overriding the
379 * {@code toString()} method in the type of the field in question would also
380 * work fine.</p>
381 *
382 * @param value The contents of the field currently being processed from the
383 * bean to be written. Can be null if the field is not marked as required.
384 * @return A string representation of the value of the field in question in
385 * the bean passed in, or an empty string if {@code value} is null
386 * @throws CsvDataTypeMismatchException This implementation does not throw
387 * this exception
388 * @throws CsvRequiredFieldEmptyException If the input is empty but the
389 * field is required. The case of the field being null is checked before
390 * this method is called, but other implementations may have other cases
391 * that are semantically equivalent to being empty, such as an empty
392 * collection. The preferred way to perform this check is in
393 * {@link #isFieldEmptyForWrite(java.lang.Object) }. This exception may
394 * be removed from this method signature sometime in the future.
395 * @see #write(java.lang.Object, java.lang.Object)
396 * @since 3.9
397 */
398 protected String convertToWrite(Object value)
399 throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException {
400 // Since we have no concept of which field is required at this level,
401 // we can't check for null and throw an exception.
402 return Objects.toString(value, StringUtils.EMPTY);
403 }
404 }