/*
 * Copyright 2017 Andrew Rucker Jones.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opencsv.bean;

import com.opencsv.ICSVParser;
import com.opencsv.bean.util.OpencsvUtils;
import com.opencsv.exceptions.CsvBadConverterException;
import com.opencsv.exceptions.CsvBeanIntrospectionException;
import com.opencsv.exceptions.CsvConstraintViolationException;
import com.opencsv.exceptions.CsvDataTypeMismatchException;
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.SortedBag;
import org.apache.commons.collections4.bag.HashBag;
import org.apache.commons.collections4.bag.TreeBag;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

This class concerns itself with handling collection-valued bean fields.
Author:Andrew Rucker Jones
Type parameters:
  • <T> – The type of the bean being populated
  • <I> – Type of the index into a multivalued field
Since:4.2
/** * This class concerns itself with handling collection-valued bean fields. * * @param <T> The type of the bean being populated * @param <I> Type of the index into a multivalued field * @author Andrew Rucker Jones * @since 4.2 */
public class BeanFieldSplit<T, I> extends AbstractBeanField<T, I> { private final Pattern splitOn, capture; private final String writeDelimiter, writeFormat; private final Class<? extends Collection> collectionType; private final Class<?> elementType;
The only valid constructor.
Params:
/** * The only valid constructor. * * @param type The type of the class in which this field is found. This is * the type as instantiated by opencsv, and not necessarily the * type in which the field is declared in the case of * inheritance. * @param field A {@link java.lang.reflect.Field} object. * @param required Whether or not this field is required in input * @param errorLocale The errorLocale to use for error messages. * @param converter The converter to be used to perform the actual data * conversion * @param splitOn See {@link CsvBindAndSplitByName#splitOn()} * @param writeDelimiter See {@link CsvBindAndSplitByName#writeDelimiter()} * @param collectionType See {@link CsvBindAndSplitByName#collectionType()} * @param elementType See {@link CsvBindAndSplitByName#elementType()} * @param capture See {@link CsvBindAndSplitByName#capture()} * @param format The format string used for packaging values to be written. * If {@code null} or empty, it is ignored. */
public BeanFieldSplit( Class<?> type, Field field, boolean required, Locale errorLocale, CsvConverter converter, String splitOn, String writeDelimiter, Class<? extends Collection> collectionType, Class<?> elementType, String capture, String format) { // Simple assignments super(type, field, required, errorLocale, converter); this.writeDelimiter = writeDelimiter; this.writeFormat = format; this.elementType = elementType; // Check that we really have a collection if(!Collection.class.isAssignableFrom(field.getType())) { throw new CsvBadConverterException( BeanFieldSplit.class, String.format( ResourceBundle.getBundle( ICSVParser.DEFAULT_BUNDLE_NAME, this.errorLocale).getString("invalid.collection.type"), field.getType().toString())); } // Check the regular expressions for validity and compile once for speed this.splitOn = OpencsvUtils.compilePattern(splitOn, 0, BeanFieldSplit.class, this.errorLocale); this.capture = OpencsvUtils.compilePatternAtLeastOneGroup(capture, 0, BeanFieldSplit.class, this.errorLocale); // Verify that the format string works as expected OpencsvUtils.verifyFormatString(this.writeFormat, BeanFieldSplit.class, this.errorLocale); // Determine the Collection implementation that should be instantiated // for every bean. Class<?> fieldType = field.getType(); if(!fieldType.isInterface()) { this.collectionType = (Class<Collection>)field.getType(); } else if(!collectionType.isInterface()) { this.collectionType = collectionType; } else { if(Collection.class.equals(fieldType) || List.class.equals(fieldType)) { this.collectionType = ArrayList.class; } else if(Set.class.equals(fieldType)) { if(fieldType.isEnum()) { this.collectionType = EnumSet.class; } else { this.collectionType = HashSet.class; } } else if(SortedSet.class.equals(fieldType) || NavigableSet.class.equals(fieldType)) { this.collectionType = TreeSet.class; } else if(Queue.class.equals(fieldType) || Deque.class.equals(fieldType)) { this.collectionType = ArrayDeque.class; } else if(Bag.class.equals(fieldType)) { this.collectionType = HashBag.class; } else if(SortedBag.class.equals(fieldType)) { this.collectionType = TreeBag.class; } else { this.collectionType = null; throw new CsvBadConverterException( BeanFieldSplit.class, String.format( ResourceBundle.getBundle( ICSVParser.DEFAULT_BUNDLE_NAME, this.errorLocale).getString("invalid.collection.type"), collectionType.toString())); } } // Now that we know what type we want to assign, run one last check // that assignment is truly possible if(!field.getType().isAssignableFrom(this.collectionType)) { throw new CsvBadConverterException( BeanFieldSplit.class, String.format( ResourceBundle.getBundle( ICSVParser.DEFAULT_BUNDLE_NAME, this.errorLocale).getString("unassignable.collection.type"), collectionType.getName(), field.getType().getName())); } }
This method manages the collection being created as well as splitting the data. Once the data are split, they are sent to the converter for the actual conversion.
See Also:
/** * This method manages the collection being created as well as splitting the * data. * Once the data are split, they are sent to the converter for the actual * conversion. * * @see ConverterPrimitiveTypes#convertToRead(java.lang.String) * @see ConverterDate#convertToRead(java.lang.String) * @see ConverterNumber#convertToRead(String) */
// The rest of the Javadoc is inherited @Override protected Object convert(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException { Collection<Object> collection; try { if(collectionType.equals(EnumSet.class)) { collection = (Collection)EnumSet.noneOf((Class<Enum>)elementType); } else { collection = collectionType.newInstance(); } } catch(InstantiationException | IllegalAccessException e) { CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException( String.format( ResourceBundle .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("collection.cannot.be.instantiated"), collectionType.getCanonicalName())); csve.initCause(e); throw csve; } String[] splitValues = value == null ? ArrayUtils.EMPTY_STRING_ARRAY : splitOn.split(value); for(String s : splitValues) { if(capture != null) { Matcher m = capture.matcher(s); if(m.matches()) { s = m.group(1); } // Otherwise s remains intentionally unchanged } collection.add(converter.convertToRead(s)); } return collection; }
Manages converting a collection of values into a single string. The conversion of each individual value is performed by the converter.
/** * Manages converting a collection of values into a single string. * The conversion of each individual value is performed by the converter. */
// The rest of the Javadoc is inherited @Override protected String convertToWrite(Object value) throws CsvDataTypeMismatchException { String retval = StringUtils.EMPTY; if(value != null) { @SuppressWarnings("unchecked") Collection<Object> collection = (Collection<Object>) value; String[] convertedValue = new String[collection.size()]; int i = 0; for(Object o : collection) { convertedValue[i] = converter.convertToWrite(o); if(StringUtils.isNotEmpty(this.writeFormat) && StringUtils.isNotEmpty(convertedValue[i])) { convertedValue[i] = String.format(this.writeFormat, convertedValue[i]); } i++; } retval = StringUtils.join(convertedValue, writeDelimiter); // String.join() make null into "null" } return retval; }
Checks that value is not null and not an empty Collection.
/** * Checks that {@code value} is not null and not an empty * {@link java.util.Collection}. */
// The rest of the Javadoc is inherited @SuppressWarnings("unchecked") @Override protected boolean isFieldEmptyForWrite(Object value) { return super.isFieldEmptyForWrite(value) || ((Collection<Object>)value).isEmpty(); } }