All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opencsv.bean.AbstractBeanField Maven / Gradle / Ivy

There is a newer version: 5.9
Show newest version
/*
 * Copyright 2016 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.processor.PreAssignmentProcessor;
import com.opencsv.bean.processor.StringProcessor;
import com.opencsv.bean.validators.PreAssignmentValidator;
import com.opencsv.bean.validators.StringValidator;
import com.opencsv.exceptions.*;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;

/**
 * This base bean takes over the responsibility of converting the supplied
 * string to the proper type for the destination field and setting the
 * destination field.
 * 

All custom converters must be descended from this class.

*

Internally, opencsv uses another set of classes for the actual conversion, * leaving this class mostly to deal with assigment to bean fields.

* * @param Type of the bean being populated * @param Type of the index into a multivalued field * @author Andrew Rucker Jones * @since 3.8 */ abstract public class AbstractBeanField implements BeanField { /** * The type the field is located in. * This is not necessarily the declaring class in the case of inheritance, * but rather the type that opencsv expects to instantiate. */ protected Class type; /** * The field this class represents. */ protected Field field; /** * Whether or not this field is required. */ protected boolean required; /** * Locale for error messages. */ protected Locale errorLocale; /** * A class that converts from a string to the destination type on reading * and vice versa on writing. * This is only used for opencsv-internal conversions, not by custom * converters. */ protected CsvConverter converter; /** * An encapsulated way of accessing the member variable associated with this * field. */ protected FieldAccess fieldAccess; /** * Default nullary constructor, so derived classes aren't forced to create * a constructor identical to this one. */ public AbstractBeanField() { required = false; errorLocale = Locale.getDefault(); } /** * @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 * @since 4.2 */ public AbstractBeanField(Class type, Field field, boolean required, Locale errorLocale, CsvConverter converter) { this.type = type; this.field = field; this.required = required; // Once we support Java 9, we can replace ObjectUtils.defaultIfNull() with Objects.requireNonNullElse() this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault()); this.converter = converter; fieldAccess = new FieldAccess<>(this.field); } @Override public Class getType() { return type; } @Override public void setType(Class type) { this.type = type; } @Override public void setField(Field field) { this.field = field; fieldAccess = new FieldAccess<>(this.field); } @Override public Field getField() { return this.field; } @Override public boolean isRequired() { return required; } @Override public void setRequired(boolean required) { this.required = required; } @Override public void setErrorLocale(Locale errorLocale) { this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault()); if (converter != null) { converter.setErrorLocale(this.errorLocale); } } @Override public Locale getErrorLocale() { return this.errorLocale; } @Override public final void setFieldValue(Object bean, String value, String header) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException, CsvConstraintViolationException, CsvValidationException { if (required && StringUtils.isBlank(value)) { throw new CsvRequiredFieldEmptyException( bean.getClass(), field, String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("required.field.empty"), field.getName())); } PreAssignmentProcessor[] processors = field.getAnnotationsByType(PreAssignmentProcessor.class); String fieldValue = value; for (PreAssignmentProcessor processor : processors) { fieldValue = preProcessValue(processor, fieldValue); } PreAssignmentValidator[] validators = field.getAnnotationsByType(PreAssignmentValidator.class); for (PreAssignmentValidator validator : validators) { validateValue(validator, fieldValue); } assignValueToField(bean, convert(fieldValue), header); } private String preProcessValue(PreAssignmentProcessor processor, String value) throws CsvValidationException { try { StringProcessor stringProcessor = processor.processor().newInstance(); stringProcessor.setParameterString(processor.paramString()); return stringProcessor.processString(value); } catch (InstantiationException | IllegalAccessException e) { throw new CsvValidationException(String.format( ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("validator.instantiation.impossible"), processor.processor().getName(), field.getName())); } } private void validateValue(PreAssignmentValidator validator, String value) throws CsvValidationException { try { StringValidator stringValidator = validator.validator().newInstance(); stringValidator.setParameterString(validator.paramString()); stringValidator.validate(value, this); } catch (InstantiationException | IllegalAccessException e) { throw new CsvValidationException(String.format( ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("validator.instantiation.impossible"), validator.validator().getName(), field.getName())); } } @Override public Object getFieldValue(Object bean) { Object o = null; try { o = fieldAccess.getField(bean); } catch(IllegalAccessException | InvocationTargetException e) { // Our testing indicates these exceptions probably can't be thrown, // but they're declared, so we have to deal with them. It's an // alibi catch block. CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException( bean, field, String.format(ResourceBundle.getBundle( ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("error.introspecting.field"), field.getName(), bean.getClass().toString())); csve.initCause(e); throw csve; } return o; } /** * @return {@code value} wrapped in an array, since we assume most values * will not be multi-valued * @since 4.2 */ // The rest of the Javadoc is inherited @Override public Object[] indexAndSplitMultivaluedField(Object value, I index) throws CsvDataTypeMismatchException { return new Object[]{value}; } /** * Whether or not this implementation of {@link BeanField} considers the * value passed in as empty for the purposes of determining whether or not * a required field is empty. *

This allows any overriding class to define "empty" while writing * values to a CSV file in a way that is meaningful for its own data. A * simple example is a {@link java.util.Collection} that is not null, but * empty.

*

The default implementation simply checks for {@code null}.

* * @param value The value of a field out of a bean that is being written to * a CSV file. Can be {@code null}. * @return Whether or not this implementation considers {@code value} to be * empty for the purposes of its conversion * @since 4.2 */ protected boolean isFieldEmptyForWrite(Object value) { return value == null; } /** * Assigns the given object to this field of the destination bean. *

Uses the setter method if available.

*

Derived classes can override this method if they have special needs * for setting the value of a field, such as adding to an existing * collection.

* * @param bean The bean in which the field is located * @param obj The data to be assigned to this field of the destination bean * @param header The header from the CSV file under which this value was found. * @throws CsvDataTypeMismatchException If the data to be assigned cannot * be converted to the type of the destination field */ protected void assignValueToField(Object bean, Object obj, String header) throws CsvDataTypeMismatchException { // obj == null means that the source field was empty. Then we simply // leave the field as it was initialized by the VM. For primitives, // that will be values like 0, and for objects it will be null. if (obj != null) { try { fieldAccess.setField(bean, obj); } catch (InvocationTargetException | IllegalAccessException e) { CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(bean, field, e.getLocalizedMessage()); csve.initCause(e); throw csve; } catch (IllegalArgumentException e2) { CsvDataTypeMismatchException csve = new CsvDataTypeMismatchException(obj, field.getType()); csve.initCause(e2); throw csve; } } } /** * Method for converting from a string to the proper datatype of the * destination field. * This method must be specified in all non-abstract derived classes. * * @param value The string from the selected field of the CSV file. If the * field is marked as required in the annotation, this value is guaranteed * not to be null, empty or blank according to * {@link org.apache.commons.lang3.StringUtils#isBlank(java.lang.CharSequence)} * @return An {@link java.lang.Object} representing the input data converted * into the proper type * @throws CsvDataTypeMismatchException If the input string cannot be converted into * the proper type * @throws CsvConstraintViolationException When the internal structure of * data would be violated by the data in the CSV file */ protected abstract Object convert(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException; /** * This method takes the current value of the field in question in the bean * passed in and converts it to a string. * It is actually a stub that calls {@link #convertToWrite(java.lang.Object)} * for the actual conversion, and itself performs validation and handles * exceptions thrown by {@link #convertToWrite(java.lang.Object)}. The * validation consists of verifying that both {@code bean} and {@link #field} * are not null before calling {@link #convertToWrite(java.lang.Object)}. */ // The rest of the Javadoc is automatically inherited @Override public final String[] write(Object bean, I index) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { // If the input is empty, check if the field is required Object value = bean != null ? getFieldValue(bean): null; if(required && (bean == null || isFieldEmptyForWrite(value))) { throw new CsvRequiredFieldEmptyException(type, field, String.format(ResourceBundle.getBundle( ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("required.field.empty"), field.getName())); } String[] result; Object[] multivalues = indexAndSplitMultivaluedField(value, index); String[] intermediateResult = new String[multivalues.length]; try { for (int i = 0; i < multivalues.length; i++) { intermediateResult[i] = convertToWrite(multivalues[i]); } result = intermediateResult; } catch (CsvDataTypeMismatchException e) { CsvDataTypeMismatchException csve = new CsvDataTypeMismatchException( bean, field.getType(), e.getMessage()); csve.initCause(e.getCause()); throw csve; } catch (CsvRequiredFieldEmptyException e) { // Our code no longer throws this exception from here, but // rather from write() using isFieldEmptyForWrite() to determine // when to throw the exception. But user code is still allowed // to override convertToWrite() and throw this exception Class beanClass = bean == null ? null : bean.getClass(); CsvRequiredFieldEmptyException csve = new CsvRequiredFieldEmptyException( beanClass, field, e.getMessage()); csve.initCause(e.getCause()); throw csve; } return result; } /** * This is the method that actually performs the conversion from field to * string for {@link #write(java.lang.Object, java.lang.Object) } and should * be overridden in derived classes. *

The default implementation simply calls {@code toString()} on the * object in question. Derived classes will, in most cases, want to override * this method. Alternatively, for complex types, overriding the * {@code toString()} method in the type of the field in question would also * work fine.

* * @param value The contents of the field currently being processed from the * bean to be written. Can be null if the field is not marked as required. * @return A string representation of the value of the field in question in * the bean passed in, or an empty string if {@code value} is null * @throws CsvDataTypeMismatchException This implementation does not throw * this exception * @throws CsvRequiredFieldEmptyException If the input is empty but the * field is required. The case of the field being null is checked before * this method is called, but other implementations may have other cases * that are semantically equivalent to being empty, such as an empty * collection. The preferred way to perform this check is in * {@link #isFieldEmptyForWrite(java.lang.Object) }. This exception may * be removed from this method signature sometime in the future. * @see #write(java.lang.Object, java.lang.Object) * @since 3.9 */ protected String convertToWrite(Object value) throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException { // Since we have no concept of which field is required at this level, // we can't check for null and throw an exception. return Objects.toString(value, StringUtils.EMPTY); } }