AbstractMappingStrategy.java

  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. import com.opencsv.ICSVParser;
  18. import com.opencsv.exceptions.*;
  19. import org.apache.commons.collections4.ListValuedMap;
  20. import org.apache.commons.collections4.MapIterator;
  21. import org.apache.commons.collections4.MultiValuedMap;
  22. import org.apache.commons.collections4.SetUtils;
  23. import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
  24. import org.apache.commons.lang3.ArrayUtils;
  25. import org.apache.commons.lang3.ObjectUtils;
  26. import org.apache.commons.lang3.StringUtils;
  27. import org.apache.commons.lang3.reflect.FieldUtils;

  28. import java.lang.annotation.Annotation;
  29. import java.lang.reflect.Field;
  30. import java.lang.reflect.InvocationTargetException;
  31. import java.util.*;
  32. import java.util.function.Function;

  33. /**
  34.  * This class collects as many generally useful parts of the implementation
  35.  * of a mapping strategy as possible.
  36.  * <p>This mapping strategy knows of the existence of binding annotations, but
  37.  * assumes through {@link #getBindingAnnotations()} they are not in use.</p>
  38.  * <p>Anyone is welcome to use it as a base class for their own mapping
  39.  * strategies.</p>
  40.  *
  41.  * @param <T> Type of object that is being processed.
  42.  * @param <C> The type of the internal many-to-one mapping
  43.  * @param <I> The initializer type used to build the internal many-to-one mapping
  44.  * @param <K> The type of the key used for internal indexing
  45.  *
  46.  * @author Andrew Rucker Jones
  47.  * @since 4.2
  48.  */
  49. public abstract class AbstractMappingStrategy<I, K extends Comparable<K>, C extends ComplexFieldMapEntry<I, K, T>, T> implements MappingStrategy<T> {

  50.     /**
  51.      * Set of classes where recursion is not allowed.   Using HashSet because, given the large number of types, the
  52.      * contains method is quicker than an Array or ArrayList (Granted the number where Set is more efficient is different
  53.      * per Java release and system configuration).  And being a Set we are noting that each value is unique.
  54.      */
  55.     // This is easier in Java 9 with Set.of()
  56.     private static final Set<Class> FORBIDDEN_CLASSES_FOR_RECURSION = new HashSet<>(Arrays.asList(Byte.TYPE, Short.TYPE,
  57.             Integer.TYPE, Float.TYPE, Double.TYPE, Boolean.TYPE, Long.TYPE, Character.TYPE));

  58.     /** This is the class of the bean to be manipulated. */
  59.     protected Class<? extends T> type;
  60.    
  61.     /**
  62.      * Maintains a bi-directional mapping between column position(s) and header
  63.      * name.
  64.      */
  65.     protected final HeaderIndex headerIndex = new HeaderIndex();

  66.     /**
  67.      * A tree of the types encountered during recursion through the root bean
  68.      * type.
  69.      * These are only the types (and associated fields) specifically annotated
  70.      * with {@link CsvRecurse}.
  71.      */
  72.     protected RecursiveType recursiveTypeTree;

  73.     /** Storage for all manually excluded class/field pairs. */
  74.     private MultiValuedMap<Class<?>, Field> ignoredFields = new ArrayListValuedHashMap<>();

  75.     /** Locale for error messages. */
  76.     protected Locale errorLocale = Locale.getDefault();

  77.     /** The profile for configuring bean fields. */
  78.     protected String profile = StringUtils.EMPTY;

  79.     /**
  80.      * For {@link BeanField#indexAndSplitMultivaluedField(java.lang.Object, java.lang.Object)}
  81.      * it is necessary to determine which index to pass in.
  82.      *
  83.      * @param index The current column position while transmuting a bean to CSV
  84.      *              output
  85.      * @return The index to be used for this mapping strategy for
  86.      * {@link BeanField#indexAndSplitMultivaluedField(java.lang.Object, java.lang.Object) }
  87.      */
  88.     protected abstract K chooseMultivaluedFieldIndexFromHeaderIndex(int index);

  89.     /**
  90.      * Returns the {@link FieldMap} associated with this mapping strategy.
  91.      *
  92.      * @return The {@link FieldMap} used by this strategy
  93.      */
  94.     protected abstract FieldMap<I, K, ? extends C, T> getFieldMap();

  95.     /**
  96.      * Returns a set of the annotations that are used for binding in this
  97.      * mapping strategy.
  98.      * The default implementation returns the empty set.
  99.      *
  100.      * @return Annotations of the sort {@link CsvBindByName} or
  101.      * {@link CsvBindByPosition} that are relevant for binding input fields to
  102.      * bean members in this mapping strategy
  103.      * @since 5.0
  104.      */
  105.     protected Set<Class<? extends Annotation>> getBindingAnnotations() {return Collections.emptySet();}

  106.     /**
  107.      * Creates a map of annotated fields in the bean to be processed.
  108.      * <p>This method is called by {@link #loadFieldMap()} when at least one
  109.      * relevant annotation is found on a member variable.</p>
  110.      * <p>The default implementation assumes there are no annotations and does
  111.      * nothing.</p>
  112.      *
  113.      * @param fields A list of fields annotated with a binding annotation
  114.      *               in the bean to be processed
  115.      * @since 5.0
  116.      */
  117.     protected void loadAnnotatedFieldMap(ListValuedMap<Class<?>, Field> fields) {}

  118.     /**
  119.      * Creates a map of fields in the bean to be processed that have no
  120.      * annotations.
  121.      * This method is called by {@link #loadFieldMap()} when absolutely no
  122.      * annotations that are relevant for this mapping strategy are found in the
  123.      * type of bean being processed.
  124.      *
  125.      * @param fields A list of all non-synthetic fields in the bean to be
  126.      *               processed
  127.      * @since 5.0
  128.      */
  129.     protected abstract void loadUnadornedFieldMap(ListValuedMap<Class<?>, Field> fields);

  130.     /**
  131.      * Creates an empty binding-type-specific field map that can be filled in
  132.      * later steps.
  133.      * <p>This method may be called multiple times and must erase any state
  134.      * information from previous calls.</p>
  135.      *
  136.      * @since 5.0
  137.      */
  138.     protected abstract void initializeFieldMap();

  139.     /**
  140.      * Gets the field for a given column position.
  141.      *
  142.      * @param col The column to find the field for
  143.      * @return BeanField containing the field for a given column position, or
  144.      * null if one could not be found
  145.      * @throws CsvBadConverterException If a custom converter for a field cannot
  146.      *                                  be initialized
  147.      */
  148.     protected abstract BeanField<T, K> findField(int col);

  149.     /**
  150.      * Must be called once the length of input for a line/record is known to
  151.      * verify that the line was complete.
  152.      * Complete in this context means, no required fields are missing. The issue
  153.      * here is, as long as a column is present but empty, we can check whether
  154.      * the field is required and throw an exception if it is not, but if the data
  155.      * end prematurely, we never have this chance without indication that no more
  156.      * data are on the way.
  157.      * Another validation is that the number of fields must match the number of
  158.      * headers to prevent a data mismatch situation.
  159.      *
  160.      * @param numberOfFields The number of fields present in the line of input
  161.      * @throws CsvRequiredFieldEmptyException If a required column is missing
  162.      * @since 4.0
  163.      */
  164.     protected abstract void verifyLineLength(int numberOfFields) throws CsvRequiredFieldEmptyException;
  165.    
  166.     /**
  167.      * Implementation will return a bean of the type of object being mapped.
  168.      *
  169.      * @return A new instance of the class being mapped.
  170.      * @throws CsvBeanIntrospectionException Thrown on error creating object.
  171.      * @throws IllegalStateException If the type of the bean has not been
  172.      *   initialized through {@link #setType(java.lang.Class)}
  173.      */
  174.     protected Map<Class<?>, Object> createBean()
  175.             throws CsvBeanIntrospectionException, IllegalStateException {
  176.         if(type == null) {
  177.             throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("type.unset"));
  178.         }

  179.         // Create the root bean and all beans underneath it
  180.         Map<Class<?>, Object> instanceMap = new HashMap<>();
  181.         try {
  182.             T rootBean = type.newInstance();
  183.             instanceMap.put(type, rootBean);
  184.             createSubordinateBeans(recursiveTypeTree, instanceMap, rootBean);
  185.         } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
  186.             CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
  187.                     ResourceBundle.getBundle(
  188.                             ICSVParser.DEFAULT_BUNDLE_NAME,
  189.                             errorLocale)
  190.                             .getString("bean.instantiation.impossible"));
  191.             csve.initCause(e);
  192.             throw csve;
  193.         }

  194.         return instanceMap;
  195.     }

  196.     private static void createSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
  197.             throws InstantiationException, IllegalAccessException, InvocationTargetException {
  198.         for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
  199.             Object childObject = entry.getKey().getField(containingObject);
  200.             if(childObject == null) {
  201.                 childObject = entry.getValue().type.newInstance();
  202.                 entry.getKey().setField(containingObject, childObject);
  203.             }
  204.             instanceMap.put(entry.getValue().getType(), childObject);
  205.             createSubordinateBeans(entry.getValue(), instanceMap, childObject);
  206.         }
  207.     }

  208.     /**
  209.      * Creates an index of necessary types according to the mapping strategy
  210.      * and existing instances of (subordinate) beans.
  211.      *
  212.      * @param bean The root bean to be indexed
  213.      * @return The index from type to instance
  214.      * @throws IllegalAccessException If there are problems accessing a
  215.      * subordinate bean
  216.      * @throws InvocationTargetException If there are problems accessing a
  217.      * subordinate bean
  218.      * @since 5.0
  219.      */
  220.     protected Map<Class<?>, Object> indexBean(T bean)
  221.             throws IllegalAccessException, InvocationTargetException {
  222.         Map<Class<?>, Object> instanceMap = new HashMap<>();
  223.         instanceMap.put(type, bean);
  224.         indexSubordinateBeans(recursiveTypeTree, instanceMap, bean);
  225.         return instanceMap;
  226.     }

  227.     private static void indexSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
  228.             throws IllegalAccessException, InvocationTargetException {
  229.         for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
  230.             Object childObject;
  231.             if(containingObject == null) {
  232.                 childObject = null;
  233.             }
  234.             else {
  235.                 childObject = entry.getKey().getField(containingObject);
  236.             }
  237.             instanceMap.put(entry.getValue().getType(), childObject);
  238.             indexSubordinateBeans(entry.getValue(), instanceMap, childObject);
  239.         }
  240.     }

  241.     /**
  242.      * Gets the name (or position number) of the header for the given column
  243.      * number.
  244.      * The column numbers are zero-based.
  245.      *
  246.      * @param col The column number for which the header is sought
  247.      * @return The name of the header
  248.      */
  249.     public abstract String findHeader(int col);

  250.     /**
  251.      * This method generates a header that can be used for writing beans of the
  252.      * type provided back to a file.
  253.      * <p>The ordering of the headers is determined by the
  254.      * {@link com.opencsv.bean.FieldMap} in use.</p>
  255.      * <p>This method should be called first by all overriding classes to make
  256.      * certain {@link #headerIndex} is properly initialized.</p>
  257.      */
  258.     // The rest of the Javadoc is inherited
  259.     @Override
  260.     public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
  261.         if(type == null) {
  262.             throw new IllegalStateException(ResourceBundle
  263.                     .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  264.                     .getString("type.before.header"));
  265.         }
  266.        
  267.         // Always take what's been given or previously determined first.
  268.         if(headerIndex.isEmpty()) {
  269.             String[] header = getFieldMap().generateHeader(bean);
  270.             headerIndex.initializeHeaderIndex(header);
  271.             return header;
  272.         }
  273.        
  274.         // Otherwise, put headers in the right places.
  275.         return headerIndex.getHeaderIndex();
  276.     }

  277.     /**
  278.      * Get the column name for a given column position.
  279.      *
  280.      * @param col Column position.
  281.      * @return The column name or null if the position is larger than the
  282.      * header array or there are no headers defined.
  283.      */
  284.     protected String getColumnName(int col) {
  285.         // headerIndex is never null because it's final
  286.         return headerIndex.getByPosition(col);
  287.     }

  288.     /**
  289.      * Get the class type that the strategy is mapping.
  290.      *
  291.      * @return Class of the object that this {@link MappingStrategy} will create.
  292.      */
  293.     public Class<? extends T> getType() {
  294.         return type;
  295.     }

  296.     @SuppressWarnings("unchecked")
  297.     @Override
  298.     public T populateNewBean(String[] line)
  299.             throws CsvBeanIntrospectionException, CsvFieldAssignmentException,
  300.             CsvChainedException {
  301.         verifyLineLength(line.length);
  302.         Map<Class<?>, Object> beanTree = createBean();

  303.         CsvChainedException chainedException = null;
  304.         for (int col = 0; col < line.length; col++) {
  305.             try {
  306.                 setFieldValue(beanTree, line[col], col);
  307.             } catch (CsvFieldAssignmentException e) {
  308.                 if(chainedException != null) {
  309.                     chainedException.add(e);
  310.                 }
  311.                 else {
  312.                     chainedException = new CsvChainedException(e);
  313.                 }
  314.             }
  315.         }
  316.         if(chainedException != null) {
  317.             if (chainedException.hasOnlyOneException()) {
  318.                 throw chainedException.getFirstException();
  319.             }
  320.             throw chainedException;
  321.         }
  322.         return (T)beanTree.get(type);
  323.     }
  324.    
  325.     /**
  326.      * Sets the class type that is being mapped.
  327.      * Also initializes the mapping between column names and bean fields
  328.      * and attempts to create one example bean to be certain there are no
  329.      * fundamental problems with creation.
  330.      */
  331.     // The rest of the Javadoc is inherited.
  332.     @Override
  333.     public void setType(Class<? extends T> type) throws CsvBadConverterException {
  334.         this.type = type;
  335.         loadFieldMap();
  336.     }

  337.     /**
  338.      * Sets the profile this mapping strategy will use when configuring bean
  339.      * fields.
  340.      */
  341.     // The rest of the Javadoc is inherited.
  342.     @Override
  343.     public void setProfile(String profile) {
  344.         this.profile = StringUtils.defaultString(profile);
  345.     }

  346.     @Override
  347.     public void ignoreFields(MultiValuedMap<Class<?>, Field> fields)  throws IllegalArgumentException {

  348.         // Check input for consistency
  349.         if(fields == null) {
  350.             ignoredFields = new ArrayListValuedHashMap<>();
  351.         }
  352.         else {
  353.             ignoredFields = fields;
  354.             MapIterator<Class<?>, Field> it = ignoredFields.mapIterator();
  355.             it.forEachRemaining(t -> {
  356.                 final Field f = it.getValue();
  357.                 if (t == null || f == null
  358.                         || !f.getDeclaringClass().isAssignableFrom(t)) {
  359.                     throw new IllegalArgumentException(ResourceBundle.getBundle(
  360.                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  361.                             .getString("ignore.field.inconsistent"));
  362.                 }
  363.             });
  364.         }

  365.         // Reload field map
  366.         if(this.type != null) {
  367.             loadFieldMap();
  368.         }
  369.     }

  370.     /**
  371.      * Filters all fields that opencsv has been instructed to ignore and
  372.      * returns a list of the rest.
  373.      * @param type The class from which {@code fields} come. This must be the
  374.      *             class as opencsv would seek to instantiate it, which in the
  375.      *             case of inheritance is not necessarily the declaring class.
  376.      * @param fields The fields to be filtered
  377.      * @return A list of fields that exist for opencsv
  378.      */
  379.     protected List<Field> filterIgnoredFields(final Class<?> type, Field[] fields) {
  380.         final List<Field> filteredFields = new LinkedList<>();
  381.         for(Field f : fields) {
  382.             CsvIgnore ignoreAnnotation = f.getAnnotation(CsvIgnore.class);
  383.             Set<String> ignoredProfiles = ignoreAnnotation == null ?
  384.                     SetUtils.<String>emptySet() :
  385.                     new HashSet<String>(Arrays.asList(ignoreAnnotation.profiles())); // This is easier in Java 9 with Set.of()
  386.             if(!ignoredFields.containsMapping(type, f) &&
  387.                     !ignoredProfiles.contains(profile) &&
  388.                     !ignoredProfiles.contains(StringUtils.EMPTY)) {
  389.                 filteredFields.add(f);
  390.             }
  391.         }
  392.         return filteredFields;
  393.     }

  394.     /**
  395.      * Builds a map of columns from the input to fields of the bean type.
  396.      *
  397.      * @throws CsvBadConverterException If there is a problem instantiating the
  398.      *                                  custom converter for an annotated field
  399.      */
  400.     protected void loadFieldMap() throws CsvBadConverterException {

  401.         // Setup
  402.         initializeFieldMap();

  403.         // Deal with embedded classes through recursion
  404.         recursiveTypeTree = loadRecursiveClasses(this.type, new HashSet<>());

  405.         // Populate the field map according to annotations or not
  406.         Map<Boolean, ListValuedMap<Class<?>, Field>> partitionedFields = partitionFields();
  407.         if(!partitionedFields.get(Boolean.TRUE).isEmpty()) {
  408.             loadAnnotatedFieldMap(partitionedFields.get(Boolean.TRUE));
  409.         }
  410.         else {
  411.             loadUnadornedFieldMap(partitionedFields.get(Boolean.FALSE));
  412.         }
  413.     }

  414.     /**
  415.      * @param type Class to be checked
  416.      * @return Whether the type may be recursed into ({@code false}), or
  417.      *   must be considered a leaf node for recursion ({@code true}). This
  418.      *   implementation considers the boxed primitives forbidden.
  419.      */
  420.     protected boolean isForbiddenClassForRecursion(Class<?> type) {
  421.         return FORBIDDEN_CLASSES_FOR_RECURSION.contains(type);
  422.     }

  423.     /**
  424.      * Creates a tree of beans embedded in each other.
  425.      * These are the member variables annotated with {@link CsvRecurse} and
  426.      * their associated types. This method is used recursively.
  427.      *
  428.      * @param newType The type that is meant to be added to the tree
  429.      * @param encounteredTypes A set of types already encountered during
  430.      *                         recursion, as types may not be recursed into
  431.      *                         more than once.
  432.      * @return A representation of this type and all of the types beneath it in
  433.      * a tree
  434.      * @throws CsvRecursionException If recursion is attempted into a primitive
  435.      * type or a previously encountered type is added again or a member
  436.      * variable annotated with {@link CsvRecurse} is also annotated with a
  437.      * binding annotation
  438.      */
  439.     protected RecursiveType loadRecursiveClasses(Class<?> newType, Set<Class<?>> encounteredTypes) {

  440.         // We cannot recurse into primitive types
  441.         if (isForbiddenClassForRecursion(newType)) {
  442.             throw new CsvRecursionException(
  443.                     ResourceBundle.getBundle(
  444.                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  445.                             .getString("recursion.on.primitive"), newType);
  446.         }

  447.         // Guard against the same type being used twice
  448.         if(encounteredTypes.contains(newType)) {
  449.             throw new CsvRecursionException(String.format(ResourceBundle
  450.                     .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  451.                     .getString("recursive.type.encountered.twice"), newType.toString()), newType);
  452.         }
  453.         encounteredTypes.add(newType);

  454.         // Find types to recurse through
  455.         RecursiveType localRecursiveTypeTree = new RecursiveType(newType);
  456.         for(Field f : filterIgnoredFields(newType, FieldUtils.getFieldsWithAnnotation(newType, CsvRecurse.class))) {

  457.             // Types that are recursed into cannot also be bound
  458.             Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();
  459.             if(bindingAnnotations.stream().anyMatch(f::isAnnotationPresent)) {
  460.                 throw new CsvRecursionException(
  461.                         ResourceBundle.getBundle(
  462.                                 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  463.                                 .getString("recursion.binding.mutually.exclusive"),
  464.                         f.getType());
  465.             }

  466.             // Recurse into that type
  467.             localRecursiveTypeTree.addRecursiveMember(
  468.                     new FieldAccess<>(f),
  469.                     loadRecursiveClasses(f.getType(), encounteredTypes));
  470.         }

  471.         return localRecursiveTypeTree;
  472.     }

  473.     /**
  474.      * Creates a non-tree (fairly flat) representation of all of the fields
  475.      * bound from all types.
  476.      * This method is used recursively.
  477.      * @param root The root of the type tree at this level of recursion
  478.      * @param encounteredFields A collection of all fields thus far included
  479.      *                          in the new representation. This collection will
  480.      *                          be added to and is the result of this method.
  481.      */
  482.     private void assembleCompleteFieldList(RecursiveType root, final ListValuedMap<Class<?>, Field> encounteredFields) {
  483.         encounteredFields.putAll(root.type, filterIgnoredFields(root.type, FieldUtils.getAllFields(root.type)));
  484.         root.getRecursiveMembers().values().forEach(f -> assembleCompleteFieldList(f, encounteredFields));
  485.     }

  486.     /**
  487.      * Partitions all non-synthetic fields of the bean type being processed
  488.      * into annotated and non-annotated fields according to
  489.      * {@link #getBindingAnnotations()}.
  490.      *
  491.      * @return A multi-valued map (class to multiple fields in that class) in
  492.      * which all annotated fields are mapped under {@link Boolean#TRUE}, and
  493.      * all non-annotated fields are mapped under {@link Boolean#FALSE}.
  494.      * @since 5.0
  495.      */
  496.     protected Map<Boolean, ListValuedMap<Class<?>, Field>> partitionFields() {
  497.         // Get a flat list of all fields
  498.         ListValuedMap<Class<?>, Field> allFields = new ArrayListValuedHashMap<>();
  499.         assembleCompleteFieldList(recursiveTypeTree, allFields);

  500.         // Determine which annotations need be considered
  501.         final Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();

  502.         // Split the fields (with associated types) into annotated and
  503.         // non-annotated
  504.         Map<Boolean, ListValuedMap<Class<?>, Field>> returnValue = new TreeMap<>();
  505.         returnValue.put(Boolean.TRUE, new ArrayListValuedHashMap<>());
  506.         returnValue.put(Boolean.FALSE, new ArrayListValuedHashMap<>());
  507.         allFields.entries().stream()
  508.                 .filter(entry -> !entry.getValue().isSynthetic())
  509.                 .forEach(entry -> {
  510.                     if(bindingAnnotations.stream()
  511.                             .anyMatch(a -> entry.getValue().isAnnotationPresent(a))) {
  512.                         returnValue.get(Boolean.TRUE).put(entry.getKey(), entry.getValue());
  513.                     }
  514.                     else {
  515.                         returnValue.get(Boolean.FALSE).put(entry.getKey(), entry.getValue());
  516.                     }
  517.                 });
  518.         return returnValue;
  519.     }

  520.     /**
  521.      * Attempts to instantiate the class of the custom converter specified.
  522.      *
  523.      * @param converter The class for a custom converter
  524.      * @return The custom converter
  525.      * @throws CsvBadConverterException If the class cannot be instantiated
  526.      */
  527.     protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter)
  528.             throws CsvBadConverterException {
  529.         try {
  530.             BeanField<T, K> c = converter.newInstance();
  531.             c.setErrorLocale(errorLocale);
  532.             return c;
  533.         } catch (IllegalAccessException | InstantiationException oldEx) {
  534.             CsvBadConverterException newEx =
  535.                     new CsvBadConverterException(converter,
  536.                             String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), converter.getCanonicalName()));
  537.             newEx.initCause(oldEx);
  538.             throw newEx;
  539.         }
  540.     }

  541.     @Override
  542.     public void setErrorLocale(Locale errorLocale) {
  543.         this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
  544.        
  545.         // It's very possible that setType() was called first, which creates all
  546.         // of the BeanFields, so we need to go back through the list and correct
  547.         // them all.
  548.         if(getFieldMap() != null) {
  549.             getFieldMap().setErrorLocale(this.errorLocale);
  550.             getFieldMap().values().forEach(f -> f.setErrorLocale(this.errorLocale));
  551.         }
  552.     }
  553.    
  554.     /**
  555.      * Populates the field corresponding to the column position indicated of the
  556.      * bean passed in according to the rules of the mapping strategy.
  557.      * This method performs conversion on the input string and assigns the
  558.      * result to the proper field in the provided bean.
  559.      *
  560.      * @param beanTree  Object containing the field to be set.
  561.      * @param value String containing the value to set the field to.
  562.      * @param column The column position from the CSV file under which this
  563.      *   value was found.
  564.      * @throws CsvDataTypeMismatchException    When the result of data conversion returns
  565.      *                                         an object that cannot be assigned to the selected field
  566.      * @throws CsvRequiredFieldEmptyException  When a field is mandatory, but there is no
  567.      *                                         input datum in the CSV file
  568.      * @throws CsvConstraintViolationException When the internal structure of
  569.      *                                         data would be violated by the data in the CSV file
  570.      * @throws CsvValidationException If a user-supplied validator determines
  571.      * that the input is invalid
  572.      * @since 4.2
  573.      */
  574.     protected void setFieldValue(Map<Class<?>, Object> beanTree, String value, int column)
  575.             throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException,
  576.             CsvConstraintViolationException, CsvValidationException {
  577.         BeanField<T, K> beanField = findField(column);
  578.         if (beanField != null) {
  579.             Object subordinateBean = beanTree.get(beanField.getType());
  580.             beanField.setFieldValue(subordinateBean, value, findHeader(column));
  581.         }
  582.     }
  583.    
  584.     @Override
  585.     public String[] transmuteBean(T bean) throws CsvFieldAssignmentException, CsvChainedException {
  586.         int numColumns = headerIndex.findMaxIndex()+1;
  587.         BeanField<T, K> firstBeanField, subsequentBeanField;
  588.         K firstIndex, subsequentIndex;
  589.         List<String> contents = new ArrayList<>(Math.max(numColumns, 0));

  590.         // Create a map of types to instances of subordinate beans
  591.         Map<Class<?>, Object> instanceMap;
  592.         try {
  593.             instanceMap = indexBean(bean);
  594.         }
  595.         catch(IllegalAccessException | InvocationTargetException e) {
  596.             // Our testing indicates these exceptions probably can't be thrown,
  597.             // but they're declared, so we have to deal with them. It's an
  598.             // alibi catch block.
  599.             CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
  600.                     ResourceBundle.getBundle(
  601.                             ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
  602.                             .getString("error.introspecting.beans"));
  603.             csve.initCause(e);
  604.             throw csve;
  605.         }

  606.         CsvChainedException chainedException = null;
  607.         for(int i = 0; i < numColumns;) {

  608.             // Determine the first value
  609.             firstBeanField = findField(i);
  610.             firstIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
  611.             String[] fields = ArrayUtils.EMPTY_STRING_ARRAY;
  612.             if(firstBeanField != null) {
  613.                 try {
  614.                     fields = firstBeanField.write(instanceMap.get(firstBeanField.getType()), firstIndex);
  615.                 }
  616.                 catch(CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
  617.                     if(chainedException != null) {
  618.                         chainedException.add(e);
  619.                     }
  620.                     else {
  621.                         chainedException = new CsvChainedException(e);
  622.                     }
  623.                 }
  624.             }

  625.             if(fields.length == 0) {

  626.                 // Write the only value
  627.                 contents.add(StringUtils.EMPTY);
  628.                 i++; // Advance the index
  629.             }
  630.             else {

  631.                 // Multiple values. Write the first.
  632.                 contents.add(StringUtils.defaultString(fields[0]));

  633.                 // Now write the rest.
  634.                 // We must make certain that we don't write more fields
  635.                 // than we have columns of the correct type to cover them.
  636.                 int j = 1;
  637.                 int displacedIndex = i+j;
  638.                 subsequentBeanField = findField(displacedIndex);
  639.                 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
  640.                 while(j < fields.length
  641.                         && displacedIndex < numColumns
  642.                         && Objects.equals(firstBeanField, subsequentBeanField)
  643.                         && Objects.equals(firstIndex, subsequentIndex)) {
  644.                     // This field still has a header, so add it
  645.                     contents.add(StringUtils.defaultString(fields[j]));

  646.                     // Prepare for the next loop through
  647.                     displacedIndex = i + (++j);
  648.                     subsequentBeanField = findField(displacedIndex);
  649.                     subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
  650.                 }

  651.                 i = displacedIndex; // Advance the index

  652.                 // And here's where we fill in any fields that are missing to
  653.                 // cover the number of columns of the same type
  654.                 if(i < numColumns) {
  655.                     subsequentBeanField = findField(i);
  656.                     subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
  657.                     while(Objects.equals(firstBeanField, subsequentBeanField)
  658.                             && Objects.equals(firstIndex, subsequentIndex)
  659.                             && i < numColumns) {
  660.                         contents.add(StringUtils.EMPTY);
  661.                         subsequentBeanField = findField(++i);
  662.                         subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
  663.                     }
  664.                 }
  665.             }
  666.         }

  667.         // If there were exceptions, throw them
  668.         if(chainedException != null) {
  669.             if (chainedException.hasOnlyOneException()) {
  670.                 throw chainedException.getFirstException();
  671.             }
  672.             throw chainedException;
  673.         }

  674.         return contents.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
  675.     }

  676.     /**
  677.      * Given the information provided, determines the appropriate built-in
  678.      * converter to be passed in to the {@link BeanField} being created.
  679.      *
  680.      * @param field The field of the bean type in question
  681.      * @param elementType The type to be generated by the converter (on reading)
  682.      * @param locale The locale for conversion on reading. May be null or an
  683.      *               empty string if a locale is not in use.
  684.      * @param writeLocale The locale for conversion on writing. May be null or
  685.      *                    an empty string if a locale is not in use.
  686.      * @param customConverter An optional custom converter
  687.      * @return The appropriate converter for the necessary conversion
  688.      * @throws CsvBadConverterException If the converter cannot be instantiated
  689.      *
  690.      * @since 4.2
  691.      */
  692.     protected CsvConverter determineConverter(Field field,
  693.                                               Class<?> elementType, String locale, String writeLocale,
  694.                                               Class<? extends AbstractCsvConverter> customConverter)
  695.             throws CsvBadConverterException {
  696.         CsvConverter converter;

  697.         // A custom converter always takes precedence if specified.
  698.         if (customConverter != null && !customConverter.equals(AbstractCsvConverter.class)) {
  699.             try {
  700.                 converter = customConverter.newInstance();
  701.             } catch (IllegalAccessException | InstantiationException oldEx) {
  702.                 CsvBadConverterException newEx =
  703.                         new CsvBadConverterException(customConverter,
  704.                                 String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), customConverter.getCanonicalName()));
  705.                 newEx.initCause(oldEx);
  706.                 throw newEx;
  707.             }
  708.             converter.setType(elementType);
  709.             converter.setLocale(locale);
  710.             converter.setWriteLocale(writeLocale);
  711.             converter.setErrorLocale(errorLocale);
  712.         }

  713.         // Perhaps a date instead
  714.         else if (field.isAnnotationPresent(CsvDate.class) || field.isAnnotationPresent(CsvDates.class)) {
  715.             CsvDate annotation = selectAnnotationForProfile(
  716.                     field.getAnnotationsByType(CsvDate.class),
  717.                     CsvDate::profiles);
  718.             if(annotation == null) {
  719.                 throw new CsvBadConverterException(CsvDate.class, String.format(
  720.                         ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.date"),
  721.                         profile));
  722.             }
  723.             String readFormat = annotation.value();
  724.             String writeFormat = annotation.writeFormatEqualsReadFormat()
  725.                     ? readFormat : annotation.writeFormat();
  726.             String readChrono = annotation.chronology();
  727.             String writeChrono = annotation.writeChronologyEqualsReadChronology()
  728.                     ? readChrono : annotation.writeChronology();
  729.             converter = new ConverterDate(elementType, locale, writeLocale,
  730.                     errorLocale, readFormat, writeFormat, readChrono, writeChrono);
  731.         }

  732.         // Or a number
  733.         else if(field.isAnnotationPresent(CsvNumber.class) || field.isAnnotationPresent(CsvNumbers.class)) {
  734.             CsvNumber annotation = selectAnnotationForProfile(
  735.                     field.getAnnotationsByType(CsvNumber.class),
  736.                     CsvNumber::profiles);
  737.             if(annotation == null) {
  738.                 throw new CsvBadConverterException(CsvNumber.class, String.format(
  739.                         ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.number"),
  740.                         profile));
  741.             }
  742.             String readFormat = annotation.value();
  743.             String writeFormat = annotation.writeFormatEqualsReadFormat()
  744.                     ? readFormat : annotation.writeFormat();
  745.             converter = new ConverterNumber(elementType, locale, writeLocale,
  746.                     errorLocale, readFormat, writeFormat, annotation.roundingMode());
  747.         }

  748.         // or a Currency
  749.         else if (elementType.equals(java.util.Currency.class)){
  750.             converter = new ConverterCurrency(errorLocale);
  751.         }

  752.         // Or an enumeration
  753.         else if (elementType.isEnum()) {
  754.             converter = new ConverterEnum(elementType, locale, writeLocale, errorLocale);
  755.         }

  756.         // or an UUID
  757.         else if (elementType.equals(UUID.class)) {
  758.             converter = new ConverterUUID(errorLocale);
  759.         }
  760.         // Otherwise a primitive
  761.         else {
  762.             converter = new ConverterPrimitiveTypes(elementType, locale, writeLocale, errorLocale);
  763.         }

  764.         return converter;
  765.     }

  766.     /**
  767.      * Determines which one of a list of annotations applies to the currently
  768.      * selected profile.
  769.      * If no annotation specific to the profile is found, the annotation for
  770.      * the default profile is returned. If neither is found, {@code null} is
  771.      * returned.
  772.      *
  773.      * @param annotations All annotations of a given type
  774.      * @param getProfiles A function mapping an annotation of type {@code A} to
  775.      *                    the list of profiles it applies to
  776.      * @param <A> The annotation type being tested
  777.      * @return The annotation with the appropriate profile or {@code null} if
  778.      *   nothing appropriate is found
  779.      * @since 5.4
  780.      */
  781.     protected <A extends Annotation> A selectAnnotationForProfile(A[] annotations, Function<A, String[]> getProfiles) {
  782.         A defaultAnnotation = null;
  783.         String[] profilesForAnnotation;
  784.         for(A annotation : annotations) {
  785.             profilesForAnnotation = getProfiles.apply(annotation);
  786.             for(String p : profilesForAnnotation) {
  787.                 if(profile.equals(p)) {
  788.                     return annotation; // I know. Bad style. I think we can live with it once.
  789.                 }
  790.                 if(StringUtils.EMPTY.equals(p)) {
  791.                     defaultAnnotation = annotation;
  792.                 }
  793.             }
  794.         }
  795.         return defaultAnnotation;
  796.     }

  797.     /**
  798.      * Encapsulates a bean type and all of the member variables that need to be
  799.      * recursed into.
  800.      */
  801.     protected static class RecursiveType {
  802.         private final Class<?> type;
  803.         private final Map<FieldAccess<Object>, RecursiveType> recursiveMembers = new HashMap<>();

  804.         /**
  805.          * Constructs a {@link RecursiveType} with the specified type.
  806.          *
  807.          * @param type Type associated with this branch
  808.          */
  809.         protected RecursiveType(Class<?> type) {
  810.             this.type = type;
  811.         }

  812.         /**
  813.          * @return Type associated with this branch
  814.          */
  815.         public Class<?> getType() {
  816.             return type;
  817.         }

  818.         /**
  819.          * Used to add a recursive type.
  820.          *
  821.          * @param member     Field access member to add a recursive type to
  822.          * @param memberType {@link RecursiveType} to add
  823.          */
  824.         public void addRecursiveMember(FieldAccess<Object> member, RecursiveType memberType) {
  825.             recursiveMembers.put(member, memberType);
  826.         }

  827.         /**
  828.          * @return {@link Map} of field access to {@link RecursiveType}.
  829.          */
  830.         public Map<FieldAccess<Object>, RecursiveType> getRecursiveMembers() {
  831.             return recursiveMembers;
  832.         }
  833.     }
  834. }