001    /*
002     * Created on Oct 31, 2006
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2006-2009 the original author or authors.
014     */
015    package org.fest.reflect.field;
016    
017    import java.lang.reflect.Field;
018    
019    import org.fest.reflect.exception.ReflectionError;
020    import org.fest.reflect.reference.TypeRef;
021    
022    import static org.fest.reflect.util.Accessibles.*;
023    import static org.fest.util.Strings.*;
024    
025    /**
026     * Understands the use of reflection to access a field from an object.
027     * <p>
028     * The following is an example of proper usage of this class:
029     * <pre>
030     *   // Retrieves the value of the field "name"
031     *   String name = {@link org.fest.reflect.core.Reflection#field(String) field}("name").{@link FieldName#ofType(Class) ofType}(String.class).{@link FieldType#in(Object) in}(person).{@link Invoker#get() get}();
032     *
033     *   // Sets the value of the field "name" to "Yoda"
034     *   {@link org.fest.reflect.core.Reflection#field(String) field}("name").{@link FieldName#ofType(Class) ofType}(String.class).{@link FieldType#in(Object) in}(person).{@link Invoker#set(Object) set}("Yoda");
035     *
036     *   // Retrieves the value of the static field "count"
037     *   int count = {@link org.fest.reflect.core.Reflection#staticField(String) staticField}("count").{@link StaticFieldName#ofType(Class) ofType}(int.class).{@link StaticFieldType#in(Class) in}(Person.class).{@link Invoker#get() get}();
038     *
039     *   // Sets the value of the static field "count" to 3
040     *   {@link org.fest.reflect.core.Reflection#staticField(String) field}("count").{@link StaticFieldName#ofType(Class) ofType}(int.class).{@link StaticFieldType#in(Class) in}(Person.class).{@link Invoker#set(Object) set}(3);
041     * </pre>
042     * </p>
043     *
044     * @param <T> the declared type for the field to access.
045     *
046     * @author Alex Ruiz
047     */
048    public final class Invoker<T> {
049    
050      private final Object target;
051      private final Field field;
052      private final boolean accessible;
053    
054      static <T> Invoker<T> newInvoker(String fieldName, TypeRef<T> expectedType, Object target) {
055        return createInvoker(fieldName, expectedType.rawType(), target);
056      }
057    
058      static <T> Invoker<T> newInvoker(String fieldName, Class<T> expectedType, Object target) {
059        return createInvoker(fieldName, expectedType, target);
060      }
061    
062      private static <T> Invoker<T> createInvoker(String fieldName, Class<?> expectedType, Object target) {
063        if (target == null) throw new NullPointerException("Target should not be null");
064        Field field = lookupInClassHierarchy(fieldName, typeOf(target));
065        verifyCorrectType(field, expectedType);
066        return new Invoker<T>(target, field);
067      }
068    
069      private static Class<?> typeOf(Object target) {
070        if (target instanceof Class<?>) return (Class<?>)target;
071        return target.getClass();
072      }
073    
074      private static Field lookupInClassHierarchy(String fieldName, Class<?> declaringType) {
075        Field field = null;
076        Class<?> target = declaringType;
077        while (target != null) {
078          field = field(fieldName, target);
079          if (field != null) break;
080          target = target.getSuperclass();
081        }
082        if (field != null) return field;
083        throw new ReflectionError(concat("Unable to find field ", quote(fieldName), " in ", declaringType.getName()));
084      }
085    
086      private static void verifyCorrectType(Field field, Class<?> expectedType) {
087        boolean isAccessible = field.isAccessible();
088        try {
089          makeAccessible(field);
090          Class<?> actualType = field.getType();
091          if (!expectedType.isAssignableFrom(actualType)) throw incorrectFieldType(field, actualType, expectedType);
092        } finally {
093          setAccessibleIgnoringExceptions(field, isAccessible);
094        }
095      }
096    
097      private Invoker(Object target, Field field) {
098        this.target = target;
099        this.field = field;
100        accessible = field.isAccessible();
101      }
102    
103      private static Field field(String fieldName, Class<?> declaringType) {
104        try {
105          return declaringType.getDeclaredField(fieldName);
106        } catch (NoSuchFieldException e) {
107          return null;
108        }
109      }
110    
111      private static ReflectionError incorrectFieldType(Field field, Class<?> actual, Class<?> expected) {
112        String fieldTypeName = field.getDeclaringClass().getName();
113        String message = concat("The type of the field ", quote(field.getName()), " in ", fieldTypeName, " should be <",
114            expected.getName(), "> but was <", actual.getName(), ">");
115        throw new ReflectionError(message);
116      }
117    
118      /**
119       * Sets a value in the field managed by this class.
120       * @param value the value to set.
121       * @throws ReflectionError if the given value cannot be set.
122       */
123      public void set(T value) {
124        try {
125          setAccessible(field, true);
126          field.set(target, value);
127        } catch (Exception e) {
128          throw new ReflectionError(concat("Unable to update the value in field ", quote(field.getName())), e);
129        } finally {
130          setAccessibleIgnoringExceptions(field, accessible);
131        }
132      }
133    
134      /**
135       * Returns the value of the field managed by this class.
136       * @return the value of the field managed by this class.
137       * @throws ReflectionError if the value of the field cannot be retrieved.
138       */
139      @SuppressWarnings("unchecked")
140      public T get() {
141        try {
142          setAccessible(field, true);
143          return (T) field.get(target);
144        } catch (Exception e) {
145          throw new ReflectionError(concat("Unable to obtain the value in field " + quote(field.getName())), e);
146        } finally {
147          setAccessibleIgnoringExceptions(field, accessible);
148        }
149      }
150    
151      /**
152       * Returns the "real" field managed by this class.
153       * @return the "real" field managed by this class.
154       */
155      public Field info() {
156        return field;
157      }
158    }