View Javadoc
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 }