FieldAccess.java
package com.opencsv.bean;
import com.opencsv.bean.function.AccessorInvoker;
import com.opencsv.bean.function.AssignmentInvoker;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
/**
* Encapsulates the logic for accessing member variables of classes.
* <p>The logic in opencsv is always:<ol>
* <li>Use an accessor method first, if available, and this always has the
* form "get"/"set" + member name with initial capital.</li>
* <li>If this accessor method is available but deals in
* {@link java.util.Optional}, wrap or unwrap as necessary. Empty
* {@link java.util.Optional}s lead to {@code null} return values, and
* {@code null} values lead to empty {@link java.util.Optional}s.</li>
* <li>Use reflection bypassing all access control restrictions.</li>
* </ol>These are considered separately for reading and writing.</p>
*
* @param <T> The type of the member variable being accessed
* @author Andrew Rucker Jones
* @since 5.0
*/
public class FieldAccess<T> {
/** The field being accessed. */
private final Field field;
/** A functional interface to read the field. */
private final AccessorInvoker<Object, T> accessor;
/** A functional interface to write the field. */
private final AssignmentInvoker<Object, T> assignment;
/**
* Constructs this instance by determining what mode of access will work
* for this field.
*
* @param field The field to be accessed.
*/
public FieldAccess(Field field) {
this.field = field;
accessor = determineAccessorMethod();
assignment = determineAssignmentMethod();
}
@SuppressWarnings("unchecked")
private AccessorInvoker<Object, T> determineAccessorMethod() {
AccessorInvoker<Object, T> localAccessor;
String getterName = "get" + Character.toUpperCase(field.getName().charAt(0))
+ field.getName().substring(1);
try {
Method getterMethod = field.getDeclaringClass().getMethod(getterName);
if(getterMethod.getReturnType().equals(Optional.class)) {
localAccessor = bean -> {
Optional<T> opt = (Optional<T>) getterMethod.invoke(bean);
return opt.orElse(null);
};
}
else {
localAccessor = bean -> (T) getterMethod.invoke(bean);
}
} catch (NoSuchMethodException e) {
localAccessor = bean -> (T)FieldUtils.readField(this.field, bean, true);
}
return localAccessor;
}
private AssignmentInvoker<Object, T> determineAssignmentMethod() {
AssignmentInvoker<Object, T> localAssignment;
String setterName = "set" + Character.toUpperCase(field.getName().charAt(0))
+ field.getName().substring(1);
try {
Method setterMethod = field.getDeclaringClass().getMethod(setterName, field.getType());
localAssignment = setterMethod::invoke;
} catch (NoSuchMethodException e1) {
try {
Method setterMethod = field.getDeclaringClass().getMethod(setterName, Optional.class);
localAssignment = (bean, value) -> setterMethod.invoke(bean, Optional.ofNullable(value));
}
catch(NoSuchMethodException e2) {
localAssignment = (bean, value) -> FieldUtils.writeField(this.field, bean, value, true);
}
}
return localAssignment;
}
/**
* Returns the value of the field in the given bean.
* @param bean The bean from which the value of this field should be returned
* @return The value of this member variable
* @throws IllegalAccessException If there is a problem accessing the
* member variable
* @throws InvocationTargetException If there is a problem accessing the
* member variable
*/
public T getField(Object bean) throws IllegalAccessException, InvocationTargetException {
return accessor.invoke(bean);
}
/**
* Sets the value of the field in the given bean.
* @param bean The bean in which the value of the field should be set
* @param value The value to be written into the member variable of the bean
* @throws IllegalAccessException If there is a problem accessing the
* member variable
* @throws InvocationTargetException If there is a problem accessing the
* member variable
*/
public void setField(Object bean, T value) throws IllegalAccessException, InvocationTargetException{
assignment.invoke(bean, value);
}
/**
* Creates a hash code for this object.
* This override delegates hash code creation to the field passed in
* through the constructor and does not includes any of its own state
* information.
*/
@Override
public int hashCode() {
return field.hashCode();
}
/**
* Determines equality between this object and another.
* This override delegates equality determination to the field passed in
* through the constructor and does not includes any of its own state
* information.
*/
@Override
public boolean equals(Object obj) {
if(!(obj instanceof FieldAccess)) {
return false;
}
return field.equals(((FieldAccess)obj).field);
}
/**
* Returns a string representation of this object.
* This override delegates the string representation to the field passed in
* through the constructor and does not includes any of its own state
* information.
*/
@Override
public String toString() {
return field.toString();
}
}