1 package com.opencsv.bean;
2
3 import com.opencsv.bean.function.AccessorInvoker;
4 import com.opencsv.bean.function.AssignmentInvoker;
5 import org.apache.commons.lang3.reflect.FieldUtils;
6
7 import java.lang.reflect.Field;
8 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method;
10 import java.util.Optional;
11
12 /**
13 * Encapsulates the logic for accessing member variables of classes.
14 * <p>The logic in opencsv is always:<ol>
15 * <li>Use an accessor method first, if available, and this always has the
16 * form "get"/"set" + member name with initial capital.</li>
17 * <li>If this accessor method is available but deals in
18 * {@link java.util.Optional}, wrap or unwrap as necessary. Empty
19 * {@link java.util.Optional}s lead to {@code null} return values, and
20 * {@code null} values lead to empty {@link java.util.Optional}s.</li>
21 * <li>Use reflection bypassing all access control restrictions.</li>
22 * </ol>These are considered separately for reading and writing.</p>
23 *
24 * @param <T> The type of the member variable being accessed
25 * @author Andrew Rucker Jones
26 * @since 5.0
27 */
28 public class FieldAccess<T> {
29
30 /** The field being accessed. */
31 private final Field field;
32
33 /** A functional interface to read the field. */
34 private final AccessorInvoker<Object, T> accessor;
35
36 /** A functional interface to write the field. */
37 private final AssignmentInvoker<Object, T> assignment;
38
39 /**
40 * Constructs this instance by determining what mode of access will work
41 * for this field.
42 *
43 * @param field The field to be accessed.
44 */
45 public FieldAccess(Field field) {
46 this.field = field;
47 accessor = determineAccessorMethod();
48 assignment = determineAssignmentMethod();
49 }
50
51 @SuppressWarnings("unchecked")
52 private AccessorInvoker<Object, T> determineAccessorMethod() {
53 AccessorInvoker<Object, T> localAccessor;
54 String getterName = "get" + Character.toUpperCase(field.getName().charAt(0))
55 + field.getName().substring(1);
56 try {
57 Method getterMethod = field.getDeclaringClass().getMethod(getterName);
58 if(getterMethod.getReturnType().equals(Optional.class)) {
59 localAccessor = bean -> {
60 Optional<T> opt = (Optional<T>) getterMethod.invoke(bean);
61 return opt.orElse(null);
62 };
63 }
64 else {
65 localAccessor = bean -> (T) getterMethod.invoke(bean);
66 }
67 } catch (NoSuchMethodException e) {
68 localAccessor = bean -> (T)FieldUtils.readField(this.field, bean, true);
69 }
70 return localAccessor;
71 }
72
73 private AssignmentInvoker<Object, T> determineAssignmentMethod() {
74 AssignmentInvoker<Object, T> localAssignment;
75 String setterName = "set" + Character.toUpperCase(field.getName().charAt(0))
76 + field.getName().substring(1);
77 try {
78 Method setterMethod = field.getDeclaringClass().getMethod(setterName, field.getType());
79 localAssignment = setterMethod::invoke;
80 } catch (NoSuchMethodException e1) {
81 try {
82 Method setterMethod = field.getDeclaringClass().getMethod(setterName, Optional.class);
83 localAssignment = (bean, value) -> setterMethod.invoke(bean, Optional.ofNullable(value));
84 }
85 catch(NoSuchMethodException e2) {
86 localAssignment = (bean, value) -> FieldUtils.writeField(this.field, bean, value, true);
87 }
88 }
89 return localAssignment;
90 }
91
92 /**
93 * Returns the value of the field in the given bean.
94 * @param bean The bean from which the value of this field should be returned
95 * @return The value of this member variable
96 * @throws IllegalAccessException If there is a problem accessing the
97 * member variable
98 * @throws InvocationTargetException If there is a problem accessing the
99 * member variable
100 */
101 public T getField(Object bean) throws IllegalAccessException, InvocationTargetException {
102 return accessor.invoke(bean);
103 }
104
105 /**
106 * Sets the value of the field in the given bean.
107 * @param bean The bean in which the value of the field should be set
108 * @param value The value to be written into the member variable of the bean
109 * @throws IllegalAccessException If there is a problem accessing the
110 * member variable
111 * @throws InvocationTargetException If there is a problem accessing the
112 * member variable
113 */
114 public void setField(Object bean, T value) throws IllegalAccessException, InvocationTargetException{
115 assignment.invoke(bean, value);
116 }
117
118 /**
119 * Creates a hash code for this object.
120 * This override delegates hash code creation to the field passed in
121 * through the constructor and does not includes any of its own state
122 * information.
123 */
124 @Override
125 public int hashCode() {
126 return field.hashCode();
127 }
128
129 /**
130 * Determines equality between this object and another.
131 * This override delegates equality determination to the field passed in
132 * through the constructor and does not includes any of its own state
133 * information.
134 */
135 @Override
136 public boolean equals(Object obj) {
137 if(!(obj instanceof FieldAccess)) {
138 return false;
139 }
140 return field.equals(((FieldAccess)obj).field);
141 }
142
143 /**
144 * Returns a string representation of this object.
145 * This override delegates the string representation to the field passed in
146 * through the constructor and does not includes any of its own state
147 * information.
148 */
149 @Override
150 public String toString() {
151 return field.toString();
152 }
153 }