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 }