/*
 * 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.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;
The only valid constructor.
Params:
  • 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.
  • field – A Field object.
  • required – Whether or not this field is required in input
  • errorLocale – The errorLocale to use for error messages.
  • converter – The converter to be used to perform the actual data conversion
  • splitOn – See CsvBindAndSplitByName.splitOn()
  • writeDelimiter – See CsvBindAndSplitByName.writeDelimiter()
  • collectionType – See CsvBindAndSplitByName.collectionType()
  • capture – See CsvBindAndSplitByName.capture()
  • format – The format string used for packaging values to be written. If null or empty, it is ignored.
/** * 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 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, String capture, String format) { // Simple assignments super(type, field, required, errorLocale, converter); this.writeDelimiter = writeDelimiter; this.writeFormat = format; // 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)) { 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 { 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(); } }