View Javadoc
1   /*
2    * Copyright 2018 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.exceptions.*;
20  import org.apache.commons.collections4.ListValuedMap;
21  import org.apache.commons.collections4.MapIterator;
22  import org.apache.commons.collections4.MultiValuedMap;
23  import org.apache.commons.collections4.SetUtils;
24  import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
25  import org.apache.commons.lang3.ArrayUtils;
26  import org.apache.commons.lang3.ObjectUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.commons.lang3.reflect.FieldUtils;
29  
30  import java.lang.annotation.Annotation;
31  import java.lang.reflect.Field;
32  import java.lang.reflect.InvocationTargetException;
33  import java.util.*;
34  import java.util.function.Function;
35  
36  /**
37   * This class collects as many generally useful parts of the implementation
38   * of a mapping strategy as possible.
39   * <p>This mapping strategy knows of the existence of binding annotations, but
40   * assumes through {@link #getBindingAnnotations()} they are not in use.</p>
41   * <p>Anyone is welcome to use it as a base class for their own mapping
42   * strategies.</p>
43   *
44   * @param <T> Type of object that is being processed.
45   * @param <C> The type of the internal many-to-one mapping
46   * @param <I> The initializer type used to build the internal many-to-one mapping
47   * @param <K> The type of the key used for internal indexing
48   *
49   * @author Andrew Rucker Jones
50   * @since 4.2
51   */
52  public abstract class AbstractMappingStrategy<I, K extends Comparable<K>, C extends ComplexFieldMapEntry<I, K, T>, T> implements MappingStrategy<T> {
53  
54      /**
55       * Set of classes where recursion is not allowed.   Using HashSet because, given the large number of types, the
56       * contains method is quicker than an Array or ArrayList (Granted the number where Set is more efficient is different
57       * per Java release and system configuration).  And being a Set we are noting that each value is unique.
58       */
59      // This is easier in Java 9 with Set.of()
60      private static final Set<Class> FORBIDDEN_CLASSES_FOR_RECURSION = new HashSet<>(Arrays.asList(Byte.TYPE, Short.TYPE,
61              Integer.TYPE, Float.TYPE, Double.TYPE, Boolean.TYPE, Long.TYPE, Character.TYPE));
62  
63      /** This is the class of the bean to be manipulated. */
64      protected Class<? extends T> type;
65      
66      /**
67       * Maintains a bi-directional mapping between column position(s) and header
68       * name.
69       */
70      protected final HeaderIndex headerIndex = new HeaderIndex();
71  
72      /**
73       * A tree of the types encountered during recursion through the root bean
74       * type.
75       * These are only the types (and associated fields) specifically annotated
76       * with {@link CsvRecurse}.
77       */
78      protected RecursiveType recursiveTypeTree;
79  
80      /** Storage for all manually excluded class/field pairs. */
81      private MultiValuedMap<Class<?>, Field> ignoredFields = new ArrayListValuedHashMap<>();
82  
83      /** Locale for error messages. */
84      protected Locale errorLocale = Locale.getDefault();
85  
86      /** The profile for configuring bean fields. */
87      protected String profile = StringUtils.EMPTY;
88  
89      /**
90       * For {@link BeanField#indexAndSplitMultivaluedField(java.lang.Object, java.lang.Object)}
91       * it is necessary to determine which index to pass in.
92       *
93       * @param index The current column position while transmuting a bean to CSV
94       *              output
95       * @return The index to be used for this mapping strategy for
96       * {@link BeanField#indexAndSplitMultivaluedField(java.lang.Object, java.lang.Object) }
97       */
98      protected abstract K chooseMultivaluedFieldIndexFromHeaderIndex(int index);
99  
100     /**
101      * Returns the {@link FieldMap} associated with this mapping strategy.
102      *
103      * @return The {@link FieldMap} used by this strategy
104      */
105     protected abstract FieldMap<I, K, ? extends C, T> getFieldMap();
106 
107     /**
108      * Returns a set of the annotations that are used for binding in this
109      * mapping strategy.
110      * The default implementation returns the empty set.
111      *
112      * @return Annotations of the sort {@link CsvBindByName} or
113      * {@link CsvBindByPosition} that are relevant for binding input fields to
114      * bean members in this mapping strategy
115      * @since 5.0
116      */
117     protected Set<Class<? extends Annotation>> getBindingAnnotations() {return Collections.emptySet();}
118 
119     /**
120      * Creates a map of annotated fields in the bean to be processed.
121      * <p>This method is called by {@link #loadFieldMap()} when at least one
122      * relevant annotation is found on a member variable.</p>
123      * <p>The default implementation assumes there are no annotations and does
124      * nothing.</p>
125      *
126      * @param fields A list of fields annotated with a binding annotation
127      *               in the bean to be processed
128      * @since 5.0
129      */
130     protected void loadAnnotatedFieldMap(ListValuedMap<Class<?>, Field> fields) {}
131 
132     /**
133      * Creates a map of fields in the bean to be processed that have no
134      * annotations.
135      * This method is called by {@link #loadFieldMap()} when absolutely no
136      * annotations that are relevant for this mapping strategy are found in the
137      * type of bean being processed.
138      *
139      * @param fields A list of all non-synthetic fields in the bean to be
140      *               processed
141      * @since 5.0
142      */
143     protected abstract void loadUnadornedFieldMap(ListValuedMap<Class<?>, Field> fields);
144 
145     /**
146      * Creates an empty binding-type-specific field map that can be filled in
147      * later steps.
148      * <p>This method may be called multiple times and must erase any state
149      * information from previous calls.</p>
150      *
151      * @since 5.0
152      */
153     protected abstract void initializeFieldMap();
154 
155     /**
156      * Gets the field for a given column position.
157      *
158      * @param col The column to find the field for
159      * @return BeanField containing the field for a given column position, or
160      * null if one could not be found
161      * @throws CsvBadConverterException If a custom converter for a field cannot
162      *                                  be initialized
163      */
164     protected abstract BeanField<T, K> findField(int col);
165 
166     /**
167      * Must be called once the length of input for a line/record is known to
168      * verify that the line was complete.
169      * Complete in this context means, no required fields are missing. The issue
170      * here is, as long as a column is present but empty, we can check whether
171      * the field is required and throw an exception if it is not, but if the data
172      * end prematurely, we never have this chance without indication that no more
173      * data are on the way.
174      * Another validation is that the number of fields must match the number of
175      * headers to prevent a data mismatch situation.
176      *
177      * @param numberOfFields The number of fields present in the line of input
178      * @throws CsvRequiredFieldEmptyException If a required column is missing
179      * @since 4.0
180      */
181     protected abstract void verifyLineLength(int numberOfFields) throws CsvRequiredFieldEmptyException;
182     
183     /**
184      * Implementation will return a bean of the type of object being mapped.
185      *
186      * @return A new instance of the class being mapped.
187      * @throws CsvBeanIntrospectionException Thrown on error creating object.
188      * @throws IllegalStateException If the type of the bean has not been
189      *   initialized through {@link #setType(java.lang.Class)}
190      */
191     protected Map<Class<?>, Object> createBean()
192             throws CsvBeanIntrospectionException, IllegalStateException {
193         if(type == null) {
194             throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("type.unset"));
195         }
196 
197         // Create the root bean and all beans underneath it
198         Map<Class<?>, Object> instanceMap = new HashMap<>();
199         try {
200             T rootBean = type.newInstance();
201             instanceMap.put(type, rootBean);
202             createSubordinateBeans(recursiveTypeTree, instanceMap, rootBean);
203         } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
204             CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
205                     ResourceBundle.getBundle(
206                             ICSVParser.DEFAULT_BUNDLE_NAME,
207                             errorLocale)
208                             .getString("bean.instantiation.impossible"));
209             csve.initCause(e);
210             throw csve;
211         }
212 
213         return instanceMap;
214     }
215 
216     private static void createSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
217             throws InstantiationException, IllegalAccessException, InvocationTargetException {
218         for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
219             Object childObject = entry.getKey().getField(containingObject);
220             if(childObject == null) {
221                 childObject = entry.getValue().type.newInstance();
222                 entry.getKey().setField(containingObject, childObject);
223             }
224             instanceMap.put(entry.getValue().getType(), childObject);
225             createSubordinateBeans(entry.getValue(), instanceMap, childObject);
226         }
227     }
228 
229     /**
230      * Creates an index of necessary types according to the mapping strategy
231      * and existing instances of (subordinate) beans.
232      *
233      * @param bean The root bean to be indexed
234      * @return The index from type to instance
235      * @throws IllegalAccessException If there are problems accessing a
236      * subordinate bean
237      * @throws InvocationTargetException If there are problems accessing a
238      * subordinate bean
239      * @since 5.0
240      */
241     protected Map<Class<?>, Object> indexBean(T bean)
242             throws IllegalAccessException, InvocationTargetException {
243         Map<Class<?>, Object> instanceMap = new HashMap<>();
244         instanceMap.put(type, bean);
245         indexSubordinateBeans(recursiveTypeTree, instanceMap, bean);
246         return instanceMap;
247     }
248 
249     private static void indexSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
250             throws IllegalAccessException, InvocationTargetException {
251         for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
252             Object childObject;
253             if(containingObject == null) {
254                 childObject = null;
255             }
256             else {
257                 childObject = entry.getKey().getField(containingObject);
258             }
259             instanceMap.put(entry.getValue().getType(), childObject);
260             indexSubordinateBeans(entry.getValue(), instanceMap, childObject);
261         }
262     }
263 
264     /**
265      * Gets the name (or position number) of the header for the given column
266      * number.
267      * The column numbers are zero-based.
268      *
269      * @param col The column number for which the header is sought
270      * @return The name of the header
271      */
272     public abstract String findHeader(int col);
273 
274     /**
275      * This method generates a header that can be used for writing beans of the
276      * type provided back to a file.
277      * <p>The ordering of the headers is determined by the
278      * {@link com.opencsv.bean.FieldMap} in use.</p>
279      * <p>This method should be called first by all overriding classes to make
280      * certain {@link #headerIndex} is properly initialized.</p>
281      */
282     // The rest of the Javadoc is inherited
283     @Override
284     public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
285         if(type == null) {
286             throw new IllegalStateException(ResourceBundle
287                     .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
288                     .getString("type.before.header"));
289         }
290         
291         // Always take what's been given or previously determined first.
292         if(headerIndex.isEmpty()) {
293             String[] header = getFieldMap().generateHeader(bean);
294             headerIndex.initializeHeaderIndex(header);
295             return header;
296         }
297         
298         // Otherwise, put headers in the right places.
299         return headerIndex.getHeaderIndex();
300     }
301 
302     /**
303      * Get the column name for a given column position.
304      *
305      * @param col Column position.
306      * @return The column name or null if the position is larger than the
307      * header array or there are no headers defined.
308      */
309     protected String getColumnName(int col) {
310         // headerIndex is never null because it's final
311         return headerIndex.getByPosition(col);
312     }
313 
314     /**
315      * Get the class type that the strategy is mapping.
316      *
317      * @return Class of the object that this {@link MappingStrategy} will create.
318      */
319     public Class<? extends T> getType() {
320         return type;
321     }
322 
323     @SuppressWarnings("unchecked")
324     @Override
325     public T populateNewBean(String[] line)
326             throws CsvBeanIntrospectionException, CsvFieldAssignmentException,
327             CsvChainedException {
328         verifyLineLength(line.length);
329         Map<Class<?>, Object> beanTree = createBean();
330 
331         CsvChainedException chainedException = null;
332         for (int col = 0; col < line.length; col++) {
333             try {
334                 setFieldValue(beanTree, line[col], col);
335             } catch (CsvFieldAssignmentException e) {
336                 if(chainedException != null) {
337                     chainedException.add(e);
338                 }
339                 else {
340                     chainedException = new CsvChainedException(e);
341                 }
342             }
343         }
344         if(chainedException != null) {
345             if (chainedException.hasOnlyOneException()) {
346                 throw chainedException.getFirstException();
347             }
348             throw chainedException;
349         }
350         return (T)beanTree.get(type);
351     }
352     
353     /**
354      * Sets the class type that is being mapped.
355      * Also initializes the mapping between column names and bean fields
356      * and attempts to create one example bean to be certain there are no
357      * fundamental problems with creation.
358      */
359     // The rest of the Javadoc is inherited.
360     @Override
361     public void setType(Class<? extends T> type) throws CsvBadConverterException {
362         this.type = type;
363         loadFieldMap();
364     }
365 
366     /**
367      * Sets the profile this mapping strategy will use when configuring bean
368      * fields.
369      */
370     // The rest of the Javadoc is inherited.
371     @Override
372     public void setProfile(String profile) {
373         this.profile = StringUtils.defaultString(profile);
374     }
375 
376     @Override
377     public void ignoreFields(MultiValuedMap<Class<?>, Field> fields)  throws IllegalArgumentException {
378 
379         // Check input for consistency
380         if(fields == null) {
381             ignoredFields = new ArrayListValuedHashMap<>();
382         }
383         else {
384             ignoredFields = fields;
385             MapIterator<Class<?>, Field> it = ignoredFields.mapIterator();
386             it.forEachRemaining(t -> {
387                 final Field f = it.getValue();
388                 if (t == null || f == null
389                         || !f.getDeclaringClass().isAssignableFrom(t)) {
390                     throw new IllegalArgumentException(ResourceBundle.getBundle(
391                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
392                             .getString("ignore.field.inconsistent"));
393                 }
394             });
395         }
396 
397         // Reload field map
398         if(this.type != null) {
399             loadFieldMap();
400         }
401     }
402 
403     /**
404      * Filters all fields that opencsv has been instructed to ignore and
405      * returns a list of the rest.
406      * @param type The class from which {@code fields} come. This must be the
407      *             class as opencsv would seek to instantiate it, which in the
408      *             case of inheritance is not necessarily the declaring class.
409      * @param fields The fields to be filtered
410      * @return A list of fields that exist for opencsv
411      */
412     protected List<Field> filterIgnoredFields(final Class<?> type, Field[] fields) {
413         final List<Field> filteredFields = new LinkedList<>();
414         for(Field f : fields) {
415             CsvIgnore ignoreAnnotation = f.getAnnotation(CsvIgnore.class);
416             Set<String> ignoredProfiles = ignoreAnnotation == null ?
417                     SetUtils.<String>emptySet() :
418                     new HashSet<String>(Arrays.asList(ignoreAnnotation.profiles())); // This is easier in Java 9 with Set.of()
419             if(!ignoredFields.containsMapping(type, f) &&
420                     !ignoredProfiles.contains(profile) &&
421                     !ignoredProfiles.contains(StringUtils.EMPTY)) {
422                 filteredFields.add(f);
423             }
424         }
425         return filteredFields;
426     }
427 
428     /**
429      * Builds a map of columns from the input to fields of the bean type.
430      *
431      * @throws CsvBadConverterException If there is a problem instantiating the
432      *                                  custom converter for an annotated field
433      */
434     protected void loadFieldMap() throws CsvBadConverterException {
435 
436         // Setup
437         initializeFieldMap();
438 
439         // Deal with embedded classes through recursion
440         recursiveTypeTree = loadRecursiveClasses(this.type, new HashSet<>());
441 
442         // Populate the field map according to annotations or not
443         Map<Boolean, ListValuedMap<Class<?>, Field>> partitionedFields = partitionFields();
444         if(!partitionedFields.get(Boolean.TRUE).isEmpty()) {
445             loadAnnotatedFieldMap(partitionedFields.get(Boolean.TRUE));
446         }
447         else {
448             loadUnadornedFieldMap(partitionedFields.get(Boolean.FALSE));
449         }
450     }
451 
452     /**
453      * @param type Class to be checked
454      * @return Whether the type may be recursed into ({@code false}), or
455      *   must be considered a leaf node for recursion ({@code true}). This
456      *   implementation considers the boxed primitives forbidden.
457      */
458     protected boolean isForbiddenClassForRecursion(Class<?> type) {
459         return FORBIDDEN_CLASSES_FOR_RECURSION.contains(type);
460     }
461 
462     /**
463      * Creates a tree of beans embedded in each other.
464      * These are the member variables annotated with {@link CsvRecurse} and
465      * their associated types. This method is used recursively.
466      *
467      * @param newType The type that is meant to be added to the tree
468      * @param encounteredTypes A set of types already encountered during
469      *                         recursion, as types may not be recursed into
470      *                         more than once.
471      * @return A representation of this type and all of the types beneath it in
472      * a tree
473      * @throws CsvRecursionException If recursion is attempted into a primitive
474      * type or a previously encountered type is added again or a member
475      * variable annotated with {@link CsvRecurse} is also annotated with a
476      * binding annotation
477      */
478     protected RecursiveType loadRecursiveClasses(Class<?> newType, Set<Class<?>> encounteredTypes) {
479 
480         // We cannot recurse into primitive types
481         if (isForbiddenClassForRecursion(newType)) {
482             throw new CsvRecursionException(
483                     ResourceBundle.getBundle(
484                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
485                             .getString("recursion.on.primitive"), newType);
486         }
487 
488         // Guard against the same type being used twice
489         if(encounteredTypes.contains(newType)) {
490             throw new CsvRecursionException(String.format(ResourceBundle
491                     .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
492                     .getString("recursive.type.encountered.twice"), newType.toString()), newType);
493         }
494         encounteredTypes.add(newType);
495 
496         // Find types to recurse through
497         RecursiveType localRecursiveTypeTree = new RecursiveType(newType);
498         for(Field f : filterIgnoredFields(newType, FieldUtils.getFieldsWithAnnotation(newType, CsvRecurse.class))) {
499 
500             // Types that are recursed into cannot also be bound
501             Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();
502             if(bindingAnnotations.stream().anyMatch(f::isAnnotationPresent)) {
503                 throw new CsvRecursionException(
504                         ResourceBundle.getBundle(
505                                 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
506                                 .getString("recursion.binding.mutually.exclusive"),
507                         f.getType());
508             }
509 
510             // Recurse into that type
511             localRecursiveTypeTree.addRecursiveMember(
512                     new FieldAccess<>(f),
513                     loadRecursiveClasses(f.getType(), encounteredTypes));
514         }
515 
516         return localRecursiveTypeTree;
517     }
518 
519     /**
520      * Creates a non-tree (fairly flat) representation of all of the fields
521      * bound from all types.
522      * This method is used recursively.
523      * @param root The root of the type tree at this level of recursion
524      * @param encounteredFields A collection of all fields thus far included
525      *                          in the new representation. This collection will
526      *                          be added to and is the result of this method.
527      */
528     private void assembleCompleteFieldList(RecursiveType root, final ListValuedMap<Class<?>, Field> encounteredFields) {
529         encounteredFields.putAll(root.type, filterIgnoredFields(root.type, FieldUtils.getAllFields(root.type)));
530         root.getRecursiveMembers().values().forEach(f -> assembleCompleteFieldList(f, encounteredFields));
531     }
532 
533     /**
534      * Partitions all non-synthetic fields of the bean type being processed
535      * into annotated and non-annotated fields according to
536      * {@link #getBindingAnnotations()}.
537      *
538      * @return A multi-valued map (class to multiple fields in that class) in
539      * which all annotated fields are mapped under {@link Boolean#TRUE}, and
540      * all non-annotated fields are mapped under {@link Boolean#FALSE}.
541      * @since 5.0
542      */
543     protected Map<Boolean, ListValuedMap<Class<?>, Field>> partitionFields() {
544         // Get a flat list of all fields
545         ListValuedMap<Class<?>, Field> allFields = new ArrayListValuedHashMap<>();
546         assembleCompleteFieldList(recursiveTypeTree, allFields);
547 
548         // Determine which annotations need be considered
549         final Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();
550 
551         // Split the fields (with associated types) into annotated and
552         // non-annotated
553         Map<Boolean, ListValuedMap<Class<?>, Field>> returnValue = new TreeMap<>();
554         returnValue.put(Boolean.TRUE, new ArrayListValuedHashMap<>());
555         returnValue.put(Boolean.FALSE, new ArrayListValuedHashMap<>());
556         allFields.entries().stream()
557                 .filter(entry -> !entry.getValue().isSynthetic())
558                 .forEach(entry -> {
559                     if(bindingAnnotations.stream()
560                             .anyMatch(a -> entry.getValue().isAnnotationPresent(a))) {
561                         returnValue.get(Boolean.TRUE).put(entry.getKey(), entry.getValue());
562                     }
563                     else {
564                         returnValue.get(Boolean.FALSE).put(entry.getKey(), entry.getValue());
565                     }
566                 });
567         return returnValue;
568     }
569 
570     /**
571      * Attempts to instantiate the class of the custom converter specified.
572      *
573      * @param converter The class for a custom converter
574      * @return The custom converter
575      * @throws CsvBadConverterException If the class cannot be instantiated
576      */
577     protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter)
578             throws CsvBadConverterException {
579         try {
580             BeanField<T, K> c = converter.newInstance();
581             c.setErrorLocale(errorLocale);
582             return c;
583         } catch (IllegalAccessException | InstantiationException oldEx) {
584             CsvBadConverterException newEx =
585                     new CsvBadConverterException(converter,
586                             String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), converter.getCanonicalName()));
587             newEx.initCause(oldEx);
588             throw newEx;
589         }
590     }
591 
592     @Override
593     public void setErrorLocale(Locale errorLocale) {
594         this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
595         
596         // It's very possible that setType() was called first, which creates all
597         // of the BeanFields, so we need to go back through the list and correct
598         // them all.
599         if(getFieldMap() != null) {
600             getFieldMap().setErrorLocale(this.errorLocale);
601             getFieldMap().values().forEach(f -> f.setErrorLocale(this.errorLocale));
602         }
603     }
604     
605     /**
606      * Populates the field corresponding to the column position indicated of the
607      * bean passed in according to the rules of the mapping strategy.
608      * This method performs conversion on the input string and assigns the
609      * result to the proper field in the provided bean.
610      *
611      * @param beanTree  Object containing the field to be set.
612      * @param value String containing the value to set the field to.
613      * @param column The column position from the CSV file under which this
614      *   value was found.
615      * @throws CsvDataTypeMismatchException    When the result of data conversion returns
616      *                                         an object that cannot be assigned to the selected field
617      * @throws CsvRequiredFieldEmptyException  When a field is mandatory, but there is no
618      *                                         input datum in the CSV file
619      * @throws CsvConstraintViolationException When the internal structure of
620      *                                         data would be violated by the data in the CSV file
621      * @throws CsvValidationException If a user-supplied validator determines
622      * that the input is invalid
623      * @since 4.2
624      */
625     protected void setFieldValue(Map<Class<?>, Object> beanTree, String value, int column)
626             throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException,
627             CsvConstraintViolationException, CsvValidationException {
628         BeanField<T, K> beanField = findField(column);
629         if (beanField != null) {
630             Object subordinateBean = beanTree.get(beanField.getType());
631             beanField.setFieldValue(subordinateBean, value, findHeader(column));
632         }
633     }
634     
635     @Override
636     public String[] transmuteBean(T bean) throws CsvFieldAssignmentException, CsvChainedException {
637         int numColumns = headerIndex.findMaxIndex()+1;
638         BeanField<T, K> firstBeanField, subsequentBeanField;
639         K firstIndex, subsequentIndex;
640         List<String> contents = new ArrayList<>(Math.max(numColumns, 0));
641 
642         // Create a map of types to instances of subordinate beans
643         Map<Class<?>, Object> instanceMap;
644         try {
645             instanceMap = indexBean(bean);
646         }
647         catch(IllegalAccessException | InvocationTargetException e) {
648             // Our testing indicates these exceptions probably can't be thrown,
649             // but they're declared, so we have to deal with them. It's an
650             // alibi catch block.
651             CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
652                     ResourceBundle.getBundle(
653                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
654                             .getString("error.introspecting.beans"));
655             csve.initCause(e);
656             throw csve;
657         }
658 
659         CsvChainedException chainedException = null;
660         for(int i = 0; i < numColumns;) {
661 
662             // Determine the first value
663             firstBeanField = findField(i);
664             firstIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
665             String[] fields = ArrayUtils.EMPTY_STRING_ARRAY;
666             if(firstBeanField != null) {
667                 try {
668                     fields = firstBeanField.write(instanceMap.get(firstBeanField.getType()), firstIndex);
669                 }
670                 catch(CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
671                     if(chainedException != null) {
672                         chainedException.add(e);
673                     }
674                     else {
675                         chainedException = new CsvChainedException(e);
676                     }
677                 }
678             }
679 
680             if(fields.length == 0) {
681 
682                 // Write the only value
683                 contents.add(StringUtils.EMPTY);
684                 i++; // Advance the index
685             }
686             else {
687 
688                 // Multiple values. Write the first.
689                 contents.add(StringUtils.defaultString(fields[0]));
690 
691                 // Now write the rest.
692                 // We must make certain that we don't write more fields
693                 // than we have columns of the correct type to cover them.
694                 int j = 1;
695                 int displacedIndex = i+j;
696                 subsequentBeanField = findField(displacedIndex);
697                 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
698                 while(j < fields.length
699                         && displacedIndex < numColumns
700                         && Objects.equals(firstBeanField, subsequentBeanField)
701                         && Objects.equals(firstIndex, subsequentIndex)) {
702                     // This field still has a header, so add it
703                     contents.add(StringUtils.defaultString(fields[j]));
704 
705                     // Prepare for the next loop through
706                     displacedIndex = i + (++j);
707                     subsequentBeanField = findField(displacedIndex);
708                     subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
709                 }
710 
711                 i = displacedIndex; // Advance the index
712 
713                 // And here's where we fill in any fields that are missing to
714                 // cover the number of columns of the same type
715                 if(i < numColumns) {
716                     subsequentBeanField = findField(i);
717                     subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
718                     while(Objects.equals(firstBeanField, subsequentBeanField)
719                             && Objects.equals(firstIndex, subsequentIndex)
720                             && i < numColumns) {
721                         contents.add(StringUtils.EMPTY);
722                         subsequentBeanField = findField(++i);
723                         subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
724                     }
725                 }
726             }
727         }
728 
729         // If there were exceptions, throw them
730         if(chainedException != null) {
731             if (chainedException.hasOnlyOneException()) {
732                 throw chainedException.getFirstException();
733             }
734             throw chainedException;
735         }
736 
737         return contents.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
738     }
739 
740     /**
741      * Given the information provided, determines the appropriate built-in
742      * converter to be passed in to the {@link BeanField} being created.
743      *
744      * @param field The field of the bean type in question
745      * @param elementType The type to be generated by the converter (on reading)
746      * @param locale The locale for conversion on reading. May be null or an
747      *               empty string if a locale is not in use.
748      * @param writeLocale The locale for conversion on writing. May be null or
749      *                    an empty string if a locale is not in use.
750      * @param customConverter An optional custom converter
751      * @return The appropriate converter for the necessary conversion
752      * @throws CsvBadConverterException If the converter cannot be instantiated
753      *
754      * @since 4.2
755      */
756     protected CsvConverter determineConverter(Field field,
757                                               Class<?> elementType, String locale, String writeLocale,
758                                               Class<? extends AbstractCsvConverter> customConverter)
759             throws CsvBadConverterException {
760         CsvConverter converter;
761 
762         // A custom converter always takes precedence if specified.
763         if (customConverter != null && !customConverter.equals(AbstractCsvConverter.class)) {
764             try {
765                 converter = customConverter.newInstance();
766             } catch (IllegalAccessException | InstantiationException oldEx) {
767                 CsvBadConverterException newEx =
768                         new CsvBadConverterException(customConverter,
769                                 String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), customConverter.getCanonicalName()));
770                 newEx.initCause(oldEx);
771                 throw newEx;
772             }
773             converter.setType(elementType);
774             converter.setLocale(locale);
775             converter.setWriteLocale(writeLocale);
776             converter.setErrorLocale(errorLocale);
777         }
778 
779         // Perhaps a date instead
780         else if (field.isAnnotationPresent(CsvDate.class) || field.isAnnotationPresent(CsvDates.class)) {
781             CsvDate annotation = selectAnnotationForProfile(
782                     field.getAnnotationsByType(CsvDate.class),
783                     CsvDate::profiles);
784             if(annotation == null) {
785                 throw new CsvBadConverterException(CsvDate.class, String.format(
786                         ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.date"),
787                         profile));
788             }
789             String readFormat = annotation.value();
790             String writeFormat = annotation.writeFormatEqualsReadFormat()
791                     ? readFormat : annotation.writeFormat();
792             String readChrono = annotation.chronology();
793             String writeChrono = annotation.writeChronologyEqualsReadChronology()
794                     ? readChrono : annotation.writeChronology();
795             converter = new ConverterDate(elementType, locale, writeLocale,
796                     errorLocale, readFormat, writeFormat, readChrono, writeChrono);
797         }
798 
799         // Or a number
800         else if(field.isAnnotationPresent(CsvNumber.class) || field.isAnnotationPresent(CsvNumbers.class)) {
801             CsvNumber annotation = selectAnnotationForProfile(
802                     field.getAnnotationsByType(CsvNumber.class),
803                     CsvNumber::profiles);
804             if(annotation == null) {
805                 throw new CsvBadConverterException(CsvNumber.class, String.format(
806                         ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.number"),
807                         profile));
808             }
809             String readFormat = annotation.value();
810             String writeFormat = annotation.writeFormatEqualsReadFormat()
811                     ? readFormat : annotation.writeFormat();
812             converter = new ConverterNumber(elementType, locale, writeLocale,
813                     errorLocale, readFormat, writeFormat, annotation.roundingMode());
814         }
815 
816         // or a Currency
817         else if (elementType.equals(java.util.Currency.class)){
818             converter = new ConverterCurrency(errorLocale);
819         }
820 
821         // Or an enumeration
822         else if (elementType.isEnum()) {
823             converter = new ConverterEnum(elementType, locale, writeLocale, errorLocale);
824         }
825 
826         // or an UUID
827         else if (elementType.equals(UUID.class)) {
828             converter = new ConverterUUID(errorLocale);
829         }
830         // Otherwise a primitive
831         else {
832             converter = new ConverterPrimitiveTypes(elementType, locale, writeLocale, errorLocale);
833         }
834 
835         return converter;
836     }
837 
838     /**
839      * Determines which one of a list of annotations applies to the currently
840      * selected profile.
841      * If no annotation specific to the profile is found, the annotation for
842      * the default profile is returned. If neither is found, {@code null} is
843      * returned.
844      *
845      * @param annotations All annotations of a given type
846      * @param getProfiles A function mapping an annotation of type {@code A} to
847      *                    the list of profiles it applies to
848      * @param <A> The annotation type being tested
849      * @return The annotation with the appropriate profile or {@code null} if
850      *   nothing appropriate is found
851      * @since 5.4
852      */
853     protected <A extends Annotation> A selectAnnotationForProfile(A[] annotations, Function<A, String[]> getProfiles) {
854         A defaultAnnotation = null;
855         String[] profilesForAnnotation;
856         for(A annotation : annotations) {
857             profilesForAnnotation = getProfiles.apply(annotation);
858             for(String p : profilesForAnnotation) {
859                 if(profile.equals(p)) {
860                     return annotation; // I know. Bad style. I think we can live with it once.
861                 }
862                 if(StringUtils.EMPTY.equals(p)) {
863                     defaultAnnotation = annotation;
864                 }
865             }
866         }
867         return defaultAnnotation;
868     }
869 
870     /**
871      * Encapsulates a bean type and all of the member variables that need to be
872      * recursed into.
873      */
874     protected static class RecursiveType {
875         private final Class<?> type;
876         private final Map<FieldAccess<Object>, RecursiveType> recursiveMembers = new HashMap<>();
877 
878         /**
879          * Constructs a {@link RecursiveType} with the specified type.
880          *
881          * @param type Type associated with this branch
882          */
883         protected RecursiveType(Class<?> type) {
884             this.type = type;
885         }
886 
887         /**
888          * @return Type associated with this branch
889          */
890         public Class<?> getType() {
891             return type;
892         }
893 
894         /**
895          * Used to add a recursive type.
896          *
897          * @param member     Field access member to add a recursive type to
898          * @param memberType {@link RecursiveType} to add
899          */
900         public void addRecursiveMember(FieldAccess<Object> member, RecursiveType memberType) {
901             recursiveMembers.put(member, memberType);
902         }
903 
904         /**
905          * @return {@link Map} of field access to {@link RecursiveType}.
906          */
907         public Map<FieldAccess<Object>, RecursiveType> getRecursiveMembers() {
908             return recursiveMembers;
909         }
910     }
911 }