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 static org.fest.reflect.util.Accessibles.*;
018 import static org.fest.util.Arrays.array;
019 import static org.fest.util.Strings.*;
020
021 import java.lang.reflect.Field;
022 import java.lang.reflect.Proxy;
023
024 import org.fest.reflect.exception.ReflectionError;
025 import org.fest.reflect.field.decorator.DecoratorInvocationHandler;
026 import org.fest.reflect.field.decorator.PostDecorator;
027 import org.fest.reflect.field.decorator.PreDecorator;
028 import org.fest.reflect.reference.TypeRef;
029
030 /**
031 * Understands the use of reflection to access a field from an object.
032 * <p>
033 * The following is an example of proper usage of this class:
034 *
035 * <pre>
036 * // Retrieves the value of the field "name"
037 * 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}();
038 *
039 * // Sets the value of the field "name" to "Yoda"
040 * {@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");
041 *
042 * // Retrieves the value of the static field "count"
043 * 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}();
044 *
045 * // Sets the value of the static field "count" to 3
046 * {@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);
047 * </pre>
048 *
049 * </p>
050 *
051 * @param <T> the declared type for the field to access.
052 *
053 * @author Alex Ruiz
054 * @author Ivan Hristov
055 */
056 public final class Invoker<T> {
057
058 private final Object target;
059 private final Field field;
060 private final boolean accessible;
061 private final Class<?> expectedType;
062
063 static <T> Invoker<T> newInvoker(String fieldName, TypeRef<T> expectedType, Object target) {
064 return createInvoker(fieldName, expectedType.rawType(), target);
065 }
066
067 static <T> Invoker<T> newInvoker(String fieldName, Class<T> expectedType, Object target) {
068 return createInvoker(fieldName, expectedType, target);
069 }
070
071 private static <T> Invoker<T> createInvoker(String fieldName, Class<?> expectedType, Object target) {
072 if (target == null) { throw new NullPointerException("Target should not be null"); }
073 Field field = lookupInClassHierarchy(fieldName, typeOf(target));
074 verifyCorrectType(field, expectedType);
075 return new Invoker<T>(target, field, expectedType);
076 }
077
078 private static Class<?> typeOf(Object target) {
079 if (target instanceof Class<?>) return (Class<?>) target;
080 return target.getClass();
081 }
082
083 private static Field lookupInClassHierarchy(String fieldName, Class<?> declaringType) {
084 Field field = null;
085 Class<?> target = declaringType;
086 while (target != null) {
087 field = field(fieldName, target);
088 if (field != null) break;
089 target = target.getSuperclass();
090 }
091 if (field != null) return field;
092 throw new ReflectionError(concat("Unable to find field ", quote(fieldName), " in ", declaringType.getName()));
093 }
094
095 private static void verifyCorrectType(Field field, Class<?> expectedType) {
096 boolean isAccessible = field.isAccessible();
097 try {
098 makeAccessible(field);
099 Class<?> actualType = field.getType();
100 if (!expectedType.isAssignableFrom(actualType)) throw incorrectFieldType(field, actualType, expectedType);
101 } finally {
102 setAccessibleIgnoringExceptions(field, isAccessible);
103 }
104 }
105
106 private Invoker(Object target, Field field, Class<?> expectedType) {
107 this.target = target;
108 this.field = field;
109 this.expectedType = expectedType;
110 accessible = field.isAccessible();
111 }
112
113 private static Field field(String fieldName, Class<?> declaringType) {
114 try {
115 return declaringType.getDeclaredField(fieldName);
116 } catch (NoSuchFieldException e) {
117 return null;
118 }
119 }
120
121 private static ReflectionError incorrectFieldType(Field field, Class<?> actual, Class<?> expected) {
122 String fieldTypeName = field.getDeclaringClass().getName();
123 String message = concat("The type of the field ", quote(field.getName()), " in ", fieldTypeName, " should be <",
124 expected.getName(), "> but was <", actual.getName(), ">");
125 throw new ReflectionError(message);
126 }
127
128 /**
129 * Sets a value in the field managed by this class.
130 *
131 * @param value the value to set.
132 * @throws ReflectionError if the given value cannot be set.
133 */
134 public void set(T value) {
135 try {
136 setAccessible(field, true);
137 field.set(target, value);
138 } catch (Exception e) {
139 throw new ReflectionError(concat("Unable to update the value in field ", quote(field.getName())), e);
140 } finally {
141 setAccessibleIgnoringExceptions(field, accessible);
142 }
143 }
144
145 /**
146 * <b>Pre</b>-decorates a targeted object's methods.
147 * <p>
148 * Each execution of a targeted object's method will be first performed on the same method of the {@code decorator}
149 * object. The result (if any) from the invocation of the targeted object's method will be returned but you can choose
150 * to return the decorator result if you want to.
151 * <p>
152 * Be aware:
153 * <li>The type of a targeted object should be an interface for this functionality to work</li>
154 * <li>Any exception caused by an invocation of a {@code decorator} object's method will result in disrupting the
155 * default program's flow</li>
156 * <p>
157 * Example: Assuming we have the following code:
158 *
159 * <pre>
160 * interface IUploadFileService {
161 * boolean upload(String file, String destination);
162 * }
163 *
164 * public class FileManager {
165 *
166 * private IUploadFileService uploadFileService;
167 * private static final String DEFAULT_DESTINATION = "http://example.org/default/destination/";
168 *
169 * public void manage(String fileName) {
170 * if( uploadFileService.upload(fileName, DEFAULT_DESTINATION) ) {
171 * System.out.println("File "+fileName+" sent to "+DEFAULT_DESTINATION);
172 * } else {
173 * System.out.println("Unable to sent "+fileName+" to "+DEFAULT_DESTINATION);
174 * }
175 * }
176 * }
177 * </pre>
178 *
179 * Let's say we want to decorate the uploadFileService.upload(...) part, so that additional functionality is executed
180 * <b>before</b> the actual uploadFileService.upload(...) logic, the following code will do the job:
181 *
182 * <pre>
183 * IUploadFileService uploadFileServiceDecorator = ...;
184 * FileManager fileManager = new FileManager();
185 *
186 * field("uploadFileService").ofType(IUploadFileService.class)
187 * .in(fileManager)
188 * .preDecorateWith(uploadFileServiceDecorator);
189 * </pre>
190 * However, if there is an exception when calling
191 * <code>uploadFileServiceDecorator.upload(fileName, DEFAULT_DESTINATION)</code> the default program's flow will be
192 * interrupted and the <code>uploadFileService.upload(fileName, DEFAULT_DESTINATION)</code> will not be executed.
193 * <p>
194 * @param decorator which methods be called before the same targeted object methods
195 * @return the {@link DecoratedInvoker} pre decorating the target field interface with given decorator.
196 */
197 public DecoratedInvoker<T> preDecorateWith(T decorator) {
198 T target = get();
199 DecoratorInvocationHandler<T> handler = new PreDecorator<T>(target, decorator);
200 @SuppressWarnings("unchecked")
201 T field = (T) Proxy.newProxyInstance(decorator.getClass().getClassLoader(), array(expectedType), handler);
202 set(field);
203 return DecoratedInvoker.newInvoker(target, decorator, expectedType, this, handler);
204 }
205
206 /**
207 * <b>Post</b>-decorates a targeted object's methods.
208 * <p>
209 * After each execution of a targeted object's method, the same method of the {@code decorator}
210 * object will be called. The result (if any) from the invocation of the targeted object's method will be returned but you can choose
211 * to return the decorator result if you want to.
212 * <p>
213 * Be aware:
214 * <li>The type of a targeted object should be an interface for this functionality to work</li>
215 * <li>Any exception caused by an invocation of a {@code decorator} object's method will result in disrupting the
216 * default program's flow</li>
217 * <p>
218 * Example: Assuming we have the following code:
219 *
220 * <pre>
221 * interface IUploadFileService {
222 * boolean upload(String file, String destination);
223 * }
224 *
225 * public class FileManager {
226 *
227 * private IUploadFileService uploadFileService;
228 * private static final String DEFAULT_DESTINATION = "http://example.org/default/destination/";
229 *
230 * public void manage(String fileName) {
231 * if( uploadFileService.upload(fileName, DEFAULT_DESTINATION) ) {
232 * System.out.println("File "+fileName+" sent to "+DEFAULT_DESTINATION);
233 * } else {
234 * System.out.println("Unable to sent "+fileName+" to "+DEFAULT_DESTINATION);
235 * }
236 * }
237 * }
238 * </pre>
239 *
240 * Let's say we want to decorate the uploadFileService.upload(...) part, so that additional functionality is executed
241 * <b>before</b> the actual uploadFileService.upload(...) logic, the following code will do the job:
242 *
243 * <pre>
244 * IUploadFileService uploadFileServiceDecorator = ...;
245 * FileManager fileManager = new FileManager();
246 *
247 * field("uploadFileService").ofType(IUploadFileService.class)
248 * .in(fileManager)
249 * .postDecorateWith(uploadFileServiceDecorator);
250 * </pre>
251 * However, if there is an exception when calling
252 * <code>uploadFileServiceDecorator.upload(fileName, DEFAULT_DESTINATION)</code> the default program's flow will be
253 * interrupted and the <code>uploadFileService.upload(fileName, DEFAULT_DESTINATION)</code> will not be executed.
254 * <p>
255 * @param decorator which methods be called after the same targeted object methods
256 * @return the {@link DecoratedInvoker} post decorating the target field interface with given decorator.
257 */
258 public DecoratedInvoker<T> postDecorateWith(T decorator) {
259 T target = get();
260 DecoratorInvocationHandler<T> handler = new PostDecorator<T>(target, decorator);
261 @SuppressWarnings("unchecked")
262 T field = (T) Proxy.newProxyInstance(decorator.getClass().getClassLoader(), array(expectedType), handler);
263 set(field);
264 return DecoratedInvoker.newInvoker(target, decorator, expectedType, this, handler);
265 }
266
267 /**
268 * Returns the value of the field managed by this class.
269 *
270 * @return the value of the field managed by this class.
271 * @throws ReflectionError if the value of the field cannot be retrieved.
272 */
273 @SuppressWarnings("unchecked")
274 public T get() {
275 try {
276 setAccessible(field, true);
277 return (T) field.get(target);
278 } catch (Exception e) {
279 throw new ReflectionError(concat("Unable to obtain the value in field " + quote(field.getName())), e);
280 } finally {
281 setAccessibleIgnoringExceptions(field, accessible);
282 }
283 }
284
285 /**
286 * Returns the "real" field managed by this class.
287 *
288 * @return the "real" field managed by this class.
289 */
290 public Field info() {
291 return field;
292 }
293 }