HeaderColumnNameMappingStrategy.java
package com.opencsv.bean;
import org.apache.commons.collections4.ListValuedMap;
import org.apache.commons.lang3.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
/*
* Copyright 2007 Kyle Miller.
*
* 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.
*/
/**
* Maps data to objects using the column names in the first row of the CSV file
* as reference. This way the column order does not matter.
*
* @param <T> Type of the bean to be returned
*/
public class HeaderColumnNameMappingStrategy<T> extends HeaderNameBaseMappingStrategy<T> {
/**
* Default constructor. Considered stable.
* @see HeaderColumnNameMappingStrategyBuilder
*/
public HeaderColumnNameMappingStrategy() {
}
/**
* Constructor to allow setting options for header name mapping.
* Not considered stable. As new options are introduced for the mapping
* strategy, they will be introduced here. You are encouraged to use
* {@link HeaderColumnNameMappingStrategyBuilder}.
*
* @param forceCorrectRecordLength If set, every record will be shortened
* or lengthened to match the number of
* headers
* @see HeaderColumnNameMappingStrategyBuilder
*/
public HeaderColumnNameMappingStrategy(boolean forceCorrectRecordLength) {
super(forceCorrectRecordLength);
}
/**
* Register a binding between a bean field and a custom converter.
*
* @param annotation The annotation attached to the bean field
* @param localType The class/type in which the field resides
* @param localField The bean field
*/
private void registerCustomBinding(CsvCustomBindByName annotation, Class<?> localType, Field localField) {
String columnName = annotation.column().toUpperCase().trim();
if(StringUtils.isEmpty(columnName)) {
columnName = localField.getName().toUpperCase();
}
@SuppressWarnings("unchecked")
Class<? extends AbstractBeanField<T, String>> converter = (Class<? extends AbstractBeanField<T, String>>)annotation
.converter();
BeanField<T, String> bean = instantiateCustomConverter(converter);
bean.setType(localType);
bean.setField(localField);
bean.setRequired(annotation.required());
fieldMap.put(columnName, bean);
}
/**
* Register a binding between a bean field and a collection converter that
* splits input into multiple values.
*
* @param annotation The annotation attached to the bean field
* @param localType The class/type in which the field resides
* @param localField The bean field
*/
private void registerSplitBinding(CsvBindAndSplitByName annotation, Class<?> localType, Field localField) {
String columnName = annotation.column().toUpperCase().trim();
String locale = annotation.locale();
String writeLocale = annotation.writeLocaleEqualsReadLocale()
? locale : annotation.writeLocale();
Class<?> elementType = annotation.elementType();
CsvConverter converter = determineConverter(
localField, elementType, locale,
writeLocale, annotation.converter());
if (StringUtils.isEmpty(columnName)) {
fieldMap.put(localField.getName().toUpperCase(),
new BeanFieldSplit<>(
localType, localField, annotation.required(),
errorLocale, converter, annotation.splitOn(),
annotation.writeDelimiter(),
annotation.collectionType(), elementType,
annotation.capture(), annotation.format()));
} else {
fieldMap.put(columnName, new BeanFieldSplit<>(
localType, localField, annotation.required(),
errorLocale, converter, annotation.splitOn(),
annotation.writeDelimiter(), annotation.collectionType(),
elementType, annotation.capture(), annotation.format()));
}
}
/**
* Register a binding between a bean field and a multi-valued converter
* that joins values from multiple columns.
*
* @param annotation The annotation attached to the bean field
* @param localType The class/type in which the field resides
* @param localField The bean field
*/
private void registerJoinBinding(CsvBindAndJoinByName annotation, Class<?> localType, Field localField) {
String columnRegex = annotation.column();
String locale = annotation.locale();
String writeLocale = annotation.writeLocaleEqualsReadLocale()
? locale : annotation.writeLocale();
CsvConverter converter = determineConverter(
localField, annotation.elementType(), locale,
writeLocale, annotation.converter());
if (StringUtils.isEmpty(columnRegex)) {
fieldMap.putComplex(localField.getName(),
new BeanFieldJoinStringIndex<>(
localType, localField, annotation.required(),
errorLocale, converter, annotation.mapType(),
annotation.capture(), annotation.format()));
} else {
fieldMap.putComplex(columnRegex, new BeanFieldJoinStringIndex<>(
localType, localField, annotation.required(), errorLocale,
converter, annotation.mapType(), annotation.capture(),
annotation.format()));
}
}
/**
* Register a binding between a bean field and a simple converter.
*
* @param annotation The annotation attached to the bean field
* @param localType The class/type in which the field resides
* @param localField The bean field
*/
private void registerBinding(CsvBindByName annotation, Class<?> localType, Field localField) {
String columnName = annotation.column().toUpperCase().trim();
String locale = annotation.locale();
String writeLocale = annotation.writeLocaleEqualsReadLocale()
? locale : annotation.writeLocale();
CsvConverter converter = determineConverter(
localField,
localField.getType(), locale,
writeLocale, null);
if (StringUtils.isEmpty(columnName)) {
fieldMap.put(localField.getName().toUpperCase(),
new BeanFieldSingleValue<>(
localType, localField, annotation.required(),
errorLocale, converter, annotation.capture(),
annotation.format()));
} else {
fieldMap.put(columnName, new BeanFieldSingleValue<>(
localType, localField, annotation.required(),
errorLocale, converter, annotation.capture(),
annotation.format()));
}
}
/**
* Creates a map of annotated fields in the bean to be processed.
* <p>This method is called by {@link #loadFieldMap()} when at least one
* relevant annotation is found on a member variable.</p>
*/
@Override
protected void loadAnnotatedFieldMap(ListValuedMap<Class<?>, Field> fields) {
for (Map.Entry<Class<?>, Field> classField : fields.entries()) {
Class<?> localType = classField.getKey();
Field localField = classField.getValue();
// Always check for a custom converter first.
if (localField.isAnnotationPresent(CsvCustomBindByName.class)
|| localField.isAnnotationPresent(CsvCustomBindByNames.class)) {
CsvCustomBindByName annotation = selectAnnotationForProfile(
localField.getAnnotationsByType(CsvCustomBindByName.class),
CsvCustomBindByName::profiles);
if(annotation != null) {
registerCustomBinding(annotation, localType, localField);
}
}
// Then check for a collection
else if(localField.isAnnotationPresent(CsvBindAndSplitByName.class)
|| localField.isAnnotationPresent(CsvBindAndSplitByNames.class)) {
CsvBindAndSplitByName annotation = selectAnnotationForProfile(
localField.getAnnotationsByType(CsvBindAndSplitByName.class),
CsvBindAndSplitByName::profiles);
if (annotation != null) {
registerSplitBinding(annotation, localType, localField);
}
}
// Then for a multi-column annotation
else if(localField.isAnnotationPresent(CsvBindAndJoinByName.class)
|| localField.isAnnotationPresent(CsvBindAndJoinByNames.class)) {
CsvBindAndJoinByName annotation = selectAnnotationForProfile(
localField.getAnnotationsByType(CsvBindAndJoinByName.class),
CsvBindAndJoinByName::profiles);
if (annotation != null) {
registerJoinBinding(annotation, localType, localField);
}
}
// Otherwise it must be CsvBindByName.
else {
CsvBindByName annotation = selectAnnotationForProfile(
localField.getAnnotationsByType(CsvBindByName.class),
CsvBindByName::profiles);
if (annotation != null) {
registerBinding(annotation, localType, localField);
}
}
}
}
/**
* Returns a set of the annotations that are used for binding in this
* mapping strategy.
* <p>In this mapping strategy, those are currently:<ul>
* <li>{@link CsvBindByName}</li>
* <li>{@link CsvCustomBindByName}</li>
* <li>{@link CsvBindAndJoinByName}</li>
* <li>{@link CsvBindAndSplitByName}</li>
* </ul></p>
*/
@Override
protected Set<Class<? extends Annotation>> getBindingAnnotations() {
// With Java 9 this can be done more easily with Set.of()
return new HashSet<>(Arrays.asList(
CsvBindByNames.class,
CsvCustomBindByNames.class,
CsvBindAndSplitByNames.class,
CsvBindAndJoinByNames.class,
CsvBindByName.class,
CsvCustomBindByName.class,
CsvBindAndSplitByName.class,
CsvBindAndJoinByName.class));
}
}