View Javadoc
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 }