liquibase.util.csv.opencsv.bean.HeaderColumnNameMappingStrategy Maven / Gradle / Ivy
package liquibase.util.csv.opencsv.bean;
import liquibase.util.StringUtils;
import liquibase.util.csv.opencsv.CSVReader;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* 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
*/
public class HeaderColumnNameMappingStrategy implements MappingStrategy {
protected String[] header;
protected Map indexLookup = new HashMap<>();
protected Map descriptorMap;
protected Map fieldMap;
protected Class type;
protected boolean annotationDriven;
protected boolean determinedIfAnnotationDriven;
/**
* Default constructor.
*/
public HeaderColumnNameMappingStrategy() {
}
/**
* Retrieves the header from the CSVReader.
*
* @param reader the CSVReader to use for header parsing
* @throws IOException - thrown on error reading from the CSVReader.
*/
@Override
public void captureHeader(CSVReader reader) throws IOException {
header = reader.readNext();
}
/**
* Creates an index map of column names to column position.
* @param values - array of header values.
*/
protected void createIndexLookup(String[] values) {
if (indexLookup.isEmpty()) {
for (int i = 0; i < values.length; i++) {
indexLookup.put(values[i], i);
}
}
}
/**
* Resets index map of column names to column position.
*/
protected void resetIndexMap() {
indexLookup.clear();
}
/**
* Gets the column index that corresponds to a specific colum name.
* If the CSV file doesn't have a header row, this method will always return
* null.
*
* @param name the column name
* @return the column index, or null if the name doesn't exist
*/
@Override
public Integer getColumnIndex(String name) {
if (null == header) {
throw new IllegalStateException("The header row hasn't been read yet.");
}
createIndexLookup(header);
return indexLookup.get(name);
}
/**
* Gets the property descriptor for a given column position.
* @param col the column to find the description for
* @return - the property descriptor for the column position or null if one could not be found.
* @throws IntrospectionException - thrown on error retrieving the property description.
*/
@Override
public PropertyDescriptor findDescriptor(int col) throws IntrospectionException {
String columnName = getColumnName(col);
return (StringUtils.trimToNull(columnName) != null) ? findDescriptor(columnName) : null;
}
/**
* Gets the field for a given column position.
*
* @param col the column to find the field for
* @return - BeanField containing the field - and whether it is mandatory - for a given column position, or null if
* one could not be found
*/
@Override
public BeanField findField(int col) {
String columnName = getColumnName(col);
return (StringUtils.trimToNull(columnName) != null) ? findField(columnName) : null;
}
/**
* Get the column name for a given column position.
*
* @param col - column position.
* @return - the column name or null if the position is larger than the header array or there is no headers defined.
*/
public String getColumnName(int col) {
return ((null != header) && (col < header.length)) ? header[col] : null;
}
/**
* Find the property descriptor for a given column.
* @param name - column name to look up.
* @return - the property descriptor for the column.
* @throws IntrospectionException - thrown on error loading the property descriptors.
*/
protected PropertyDescriptor findDescriptor(String name) throws IntrospectionException {
if (null == descriptorMap) {
descriptorMap = loadDescriptorMap(); //lazy load descriptors
}
return descriptorMap.get(name.toUpperCase().trim());
}
/**
* Find the field for a given column.
*
* @param name - the column name to look up.
* @return - BeanField containing the field - and whether it is mandatory - for the column.
*/
protected BeanField findField(String name) {
if (null == fieldMap) {
fieldMap = loadFieldMap(); //lazy load fields
}
return fieldMap.get(name.toUpperCase().trim());
}
/**
* Determines if the name of a property descriptor matches the column name.
* Currently only used by unit tests.
* @param name - name of the column.
* @param desc - property descriptor to check against
* @return - true if the name matches the name in the property descriptor.
*/
protected boolean matches(String name, PropertyDescriptor desc) {
return desc.getName().equals(name.trim());
}
/**
* builds a map of property descriptors for the Bean.
* @return - map of property descriptors
* @throws IntrospectionException - thrown on error getting information about the bean.
*/
protected Map loadDescriptorMap() throws IntrospectionException {
Map map = new HashMap<>();
PropertyDescriptor[] descriptors;
descriptors = loadDescriptors(getType());
for (PropertyDescriptor descriptor : descriptors) {
map.put(descriptor.getName().toUpperCase().trim(), descriptor);
}
return map;
}
/**
* Builds a map of fields (and whether they're mandatory) for the Bean.
*
* @return - a map of fields (and whether they're mandatory)
*/
protected Map loadFieldMap() {
Map map = new HashMap<>();
for (Field field : loadFields(getType())) {
boolean required = field.getAnnotation(CsvBind.class).required();
map.put(field.getName().toUpperCase().trim(), new BeanField(field, required));
}
return map;
}
private PropertyDescriptor[] loadDescriptors(Class cls) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(cls);
return beanInfo.getPropertyDescriptors();
}
private List loadFields(Class cls) {
List fields = new ArrayList<>();
for (Field field : cls.getDeclaredFields()) {
if (field.isAnnotationPresent(CsvBind.class)) {
fields.add(field);
}
}
return fields;
}
/**
* Creates an object to be mapped.
* @return an object of type T.
* @throws InstantiationException - thrown on error creating object.
* @throws IllegalAccessException - thrown on error creating object.
*/
@Override
public T createBean() throws InstantiationException, IllegalAccessException {
return type.newInstance();
}
/**
* get the class type that the Strategy is mapping.
* @return Class of the object that mapper will create.
*/
public Class getType() {
return type;
}
/**
* Sets the class type that is being mapped.
*
* @param type Class type.
*/
public void setType(Class type) {
this.type = type;
}
/**
* Determines whether the mapping strategy is driven by {@link liquibase.util.csv.opencsv.bean.CsvBind} annotations.
*
* @return whether the mapping strategy is driven by annotations
*/
@Override
public boolean isAnnotationDriven() {
if (!determinedIfAnnotationDriven) { // lazy load this, and only calculate it once
for (Field field : type.getDeclaredFields()) {
if (field.isAnnotationPresent(CsvBind.class)) {
annotationDriven = true;
break;
}
}
determinedIfAnnotationDriven = true;
}
return annotationDriven;
}
}