001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.util;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.Method;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.util.Assert;
026import org.springframework.util.ClassUtils;
027import org.springframework.util.MethodInvoker;
028import org.springframework.util.ObjectUtils;
029import org.springframework.util.ReflectionUtils;
030import org.springframework.util.StringUtils;
031
032/**
033 * {@code ReflectionTestUtils} is a collection of reflection-based utility
034 * methods for use in unit and integration testing scenarios.
035 *
036 * <p>There are often times when it would be beneficial to be able to set a
037 * non-{@code public} field, invoke a non-{@code public} setter method, or
038 * invoke a non-{@code public} <em>configuration</em> or <em>lifecycle</em>
039 * callback method when testing code involving, for example:
040 *
041 * <ul>
042 * <li>ORM frameworks such as JPA and Hibernate which condone the usage of
043 * {@code private} or {@code protected} field access as opposed to
044 * {@code public} setter methods for properties in a domain entity.</li>
045 * <li>Spring's support for annotations such as
046 * {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
047 * {@link javax.inject.Inject @Inject}, and
048 * {@link javax.annotation.Resource @Resource} which provides dependency
049 * injection for {@code private} or {@code protected} fields, setter methods,
050 * and configuration methods.</li>
051 * <li>Use of annotations such as {@link javax.annotation.PostConstruct @PostConstruct}
052 * and {@link javax.annotation.PreDestroy @PreDestroy} for lifecycle callback
053 * methods.</li>
054 * </ul>
055 *
056 * <p>In addition, several methods in this class provide support for {@code static}
057 * fields &mdash; for example, {@link #setField(Class, String, Object)},
058 * {@link #getField(Class, String)}, etc.
059 *
060 * @author Sam Brannen
061 * @author Juergen Hoeller
062 * @since 2.5
063 * @see ReflectionUtils
064 * @see AopTestUtils
065 */
066public abstract class ReflectionTestUtils {
067
068        private static final String SETTER_PREFIX = "set";
069
070        private static final String GETTER_PREFIX = "get";
071
072        private static final Log logger = LogFactory.getLog(ReflectionTestUtils.class);
073
074        private static final boolean springAopPresent = ClassUtils.isPresent(
075                        "org.springframework.aop.framework.Advised", ReflectionTestUtils.class.getClassLoader());
076
077
078        /**
079         * Set the {@linkplain Field field} with the given {@code name} on the
080         * provided {@code targetObject} to the supplied {@code value}.
081         * <p>This method delegates to {@link #setField(Object, String, Object, Class)},
082         * supplying {@code null} for the {@code type} argument.
083         * @param targetObject the target object on which to set the field; never {@code null}
084         * @param name the name of the field to set; never {@code null}
085         * @param value the value to set
086         */
087        public static void setField(Object targetObject, String name, Object value) {
088                setField(targetObject, name, value, null);
089        }
090
091        /**
092         * Set the {@linkplain Field field} with the given {@code name}/{@code type}
093         * on the provided {@code targetObject} to the supplied {@code value}.
094         * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)},
095         * supplying {@code null} for the {@code targetClass} argument.
096         * @param targetObject the target object on which to set the field; never {@code null}
097         * @param name the name of the field to set; may be {@code null} if
098         * {@code type} is specified
099         * @param value the value to set
100         * @param type the type of the field to set; may be {@code null} if
101         * {@code name} is specified
102         */
103        public static void setField(Object targetObject, String name, Object value, Class<?> type) {
104                setField(targetObject, null, name, value, type);
105        }
106
107        /**
108         * Set the static {@linkplain Field field} with the given {@code name} on
109         * the provided {@code targetClass} to the supplied {@code value}.
110         * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)},
111         * supplying {@code null} for the {@code targetObject} and {@code type} arguments.
112         * @param targetClass the target class on which to set the static field;
113         * never {@code null}
114         * @param name the name of the field to set; never {@code null}
115         * @param value the value to set
116         * @since 4.2
117         */
118        public static void setField(Class<?> targetClass, String name, Object value) {
119                setField(null, targetClass, name, value, null);
120        }
121
122        /**
123         * Set the static {@linkplain Field field} with the given
124         * {@code name}/{@code type} on the provided {@code targetClass} to
125         * the supplied {@code value}.
126         * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)},
127         * supplying {@code null} for the {@code targetObject} argument.
128         * @param targetClass the target class on which to set the static field;
129         * never {@code null}
130         * @param name the name of the field to set; may be {@code null} if
131         * {@code type} is specified
132         * @param value the value to set
133         * @param type the type of the field to set; may be {@code null} if
134         * {@code name} is specified
135         * @since 4.2
136         */
137        public static void setField(Class<?> targetClass, String name, Object value, Class<?> type) {
138                setField(null, targetClass, name, value, type);
139        }
140
141        /**
142         * Set the {@linkplain Field field} with the given {@code name}/{@code type}
143         * on the provided {@code targetObject}/{@code targetClass} to the supplied
144         * {@code value}.
145         * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will
146         * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing
147         * the field to be set on the ultimate target of the proxy.
148         * <p>This method traverses the class hierarchy in search of the desired
149         * field. In addition, an attempt will be made to make non-{@code public}
150         * fields <em>accessible</em>, thus allowing one to set {@code protected},
151         * {@code private}, and <em>package-private</em> fields.
152         * @param targetObject the target object on which to set the field; may be
153         * {@code null} if the field is static
154         * @param targetClass the target class on which to set the field; may
155         * be {@code null} if the field is an instance field
156         * @param name the name of the field to set; may be {@code null} if
157         * {@code type} is specified
158         * @param value the value to set
159         * @param type the type of the field to set; may be {@code null} if
160         * {@code name} is specified
161         * @since 4.2
162         * @see ReflectionUtils#findField(Class, String, Class)
163         * @see ReflectionUtils#makeAccessible(Field)
164         * @see ReflectionUtils#setField(Field, Object, Object)
165         * @see AopTestUtils#getUltimateTargetObject(Object)
166         */
167        public static void setField(Object targetObject, Class<?> targetClass, String name, Object value, Class<?> type) {
168                Assert.isTrue(targetObject != null || targetClass != null,
169                        "Either targetObject or targetClass for the field must be specified");
170
171                if (targetObject != null && springAopPresent) {
172                        targetObject = AopTestUtils.getUltimateTargetObject(targetObject);
173                }
174                if (targetClass == null) {
175                        targetClass = targetObject.getClass();
176                }
177
178                Field field = ReflectionUtils.findField(targetClass, name, type);
179                if (field == null) {
180                        throw new IllegalArgumentException(String.format(
181                                        "Could not find field '%s' of type [%s] on %s or target class [%s]", name, type,
182                                        safeToString(targetObject), targetClass));
183                }
184
185                if (logger.isDebugEnabled()) {
186                        logger.debug(String.format(
187                                        "Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type,
188                                        safeToString(targetObject), targetClass, value));
189                }
190                ReflectionUtils.makeAccessible(field);
191                ReflectionUtils.setField(field, targetObject, value);
192        }
193
194        /**
195         * Get the value of the {@linkplain Field field} with the given {@code name}
196         * from the provided {@code targetObject}.
197         * <p>This method delegates to {@link #getField(Object, Class, String)},
198         * supplying {@code null} for the {@code targetClass} argument.
199         * @param targetObject the target object from which to get the field;
200         * never {@code null}
201         * @param name the name of the field to get; never {@code null}
202         * @return the field's current value
203         * @see #getField(Class, String)
204         */
205        public static Object getField(Object targetObject, String name) {
206                return getField(targetObject, null, name);
207        }
208
209        /**
210         * Get the value of the static {@linkplain Field field} with the given
211         * {@code name} from the provided {@code targetClass}.
212         * <p>This method delegates to {@link #getField(Object, Class, String)},
213         * supplying {@code null} for the {@code targetObject} argument.
214         * @param targetClass the target class from which to get the static field;
215         * never {@code null}
216         * @param name the name of the field to get; never {@code null}
217         * @return the field's current value
218         * @since 4.2
219         * @see #getField(Object, String)
220         */
221        public static Object getField(Class<?> targetClass, String name) {
222                return getField(null, targetClass, name);
223        }
224
225        /**
226         * Get the value of the {@linkplain Field field} with the given {@code name}
227         * from the provided {@code targetObject}/{@code targetClass}.
228         * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will
229         * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing
230         * the field to be retrieved from the ultimate target of the proxy.
231         * <p>This method traverses the class hierarchy in search of the desired
232         * field. In addition, an attempt will be made to make non-{@code public}
233         * fields <em>accessible</em>, thus allowing one to get {@code protected},
234         * {@code private}, and <em>package-private</em> fields.
235         * @param targetObject the target object from which to get the field; may be
236         * {@code null} if the field is static
237         * @param targetClass the target class from which to get the field; may
238         * be {@code null} if the field is an instance field
239         * @param name the name of the field to get; never {@code null}
240         * @return the field's current value
241         * @since 4.2
242         * @see #getField(Object, String)
243         * @see #getField(Class, String)
244         * @see ReflectionUtils#findField(Class, String, Class)
245         * @see ReflectionUtils#makeAccessible(Field)
246         * @see ReflectionUtils#getField(Field, Object)
247         * @see AopTestUtils#getUltimateTargetObject(Object)
248         */
249        public static Object getField(Object targetObject, Class<?> targetClass, String name) {
250                Assert.isTrue(targetObject != null || targetClass != null,
251                        "Either targetObject or targetClass for the field must be specified");
252
253                if (targetObject != null && springAopPresent) {
254                        targetObject = AopTestUtils.getUltimateTargetObject(targetObject);
255                }
256                if (targetClass == null) {
257                        targetClass = targetObject.getClass();
258                }
259
260                Field field = ReflectionUtils.findField(targetClass, name);
261                if (field == null) {
262                        throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]",
263                                        name, safeToString(targetObject), targetClass));
264                }
265
266                if (logger.isDebugEnabled()) {
267                        logger.debug(String.format("Getting field '%s' from %s or target class [%s]", name,
268                                        safeToString(targetObject), targetClass));
269                }
270                ReflectionUtils.makeAccessible(field);
271                return ReflectionUtils.getField(field, targetObject);
272        }
273
274        /**
275         * Invoke the setter method with the given {@code name} on the supplied
276         * target object with the supplied {@code value}.
277         * <p>This method traverses the class hierarchy in search of the desired
278         * method. In addition, an attempt will be made to make non-{@code public}
279         * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
280         * {@code private}, and <em>package-private</em> setter methods.
281         * <p>In addition, this method supports JavaBean-style <em>property</em>
282         * names. For example, if you wish to set the {@code name} property on the
283         * target object, you may pass either &quot;name&quot; or
284         * &quot;setName&quot; as the method name.
285         * @param target the target object on which to invoke the specified setter
286         * method
287         * @param name the name of the setter method to invoke or the corresponding
288         * property name
289         * @param value the value to provide to the setter method
290         * @see ReflectionUtils#findMethod(Class, String, Class[])
291         * @see ReflectionUtils#makeAccessible(Method)
292         * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
293         */
294        public static void invokeSetterMethod(Object target, String name, Object value) {
295                invokeSetterMethod(target, name, value, null);
296        }
297
298        /**
299         * Invoke the setter method with the given {@code name} on the supplied
300         * target object with the supplied {@code value}.
301         * <p>This method traverses the class hierarchy in search of the desired
302         * method. In addition, an attempt will be made to make non-{@code public}
303         * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
304         * {@code private}, and <em>package-private</em> setter methods.
305         * <p>In addition, this method supports JavaBean-style <em>property</em>
306         * names. For example, if you wish to set the {@code name} property on the
307         * target object, you may pass either &quot;name&quot; or
308         * &quot;setName&quot; as the method name.
309         * @param target the target object on which to invoke the specified setter
310         * method
311         * @param name the name of the setter method to invoke or the corresponding
312         * property name
313         * @param value the value to provide to the setter method
314         * @param type the formal parameter type declared by the setter method
315         * @see ReflectionUtils#findMethod(Class, String, Class[])
316         * @see ReflectionUtils#makeAccessible(Method)
317         * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
318         */
319        public static void invokeSetterMethod(Object target, String name, Object value, Class<?> type) {
320                Assert.notNull(target, "Target object must not be null");
321                Assert.hasText(name, "Method name must not be empty");
322                Class<?>[] paramTypes = (type != null ? new Class<?>[] {type} : null);
323
324                String setterMethodName = name;
325                if (!name.startsWith(SETTER_PREFIX)) {
326                        setterMethodName = SETTER_PREFIX + StringUtils.capitalize(name);
327                }
328
329                Method method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
330                if (method == null && !setterMethodName.equals(name)) {
331                        setterMethodName = name;
332                        method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
333                }
334                if (method == null) {
335                        throw new IllegalArgumentException(String.format(
336                                        "Could not find setter method '%s' on %s with parameter type [%s]", setterMethodName,
337                                        safeToString(target), type));
338                }
339
340                if (logger.isDebugEnabled()) {
341                        logger.debug(String.format("Invoking setter method '%s' on %s with value [%s]", setterMethodName,
342                                        safeToString(target), value));
343                }
344
345                ReflectionUtils.makeAccessible(method);
346                ReflectionUtils.invokeMethod(method, target, value);
347        }
348
349        /**
350         * Invoke the getter method with the given {@code name} on the supplied
351         * target object with the supplied {@code value}.
352         * <p>This method traverses the class hierarchy in search of the desired
353         * method. In addition, an attempt will be made to make non-{@code public}
354         * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
355         * {@code private}, and <em>package-private</em> getter methods.
356         * <p>In addition, this method supports JavaBean-style <em>property</em>
357         * names. For example, if you wish to get the {@code name} property on the
358         * target object, you may pass either &quot;name&quot; or
359         * &quot;getName&quot; as the method name.
360         * @param target the target object on which to invoke the specified getter
361         * method
362         * @param name the name of the getter method to invoke or the corresponding
363         * property name
364         * @return the value returned from the invocation
365         * @see ReflectionUtils#findMethod(Class, String, Class[])
366         * @see ReflectionUtils#makeAccessible(Method)
367         * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
368         */
369        public static Object invokeGetterMethod(Object target, String name) {
370                Assert.notNull(target, "Target object must not be null");
371                Assert.hasText(name, "Method name must not be empty");
372
373                String getterMethodName = name;
374                if (!name.startsWith(GETTER_PREFIX)) {
375                        getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
376                }
377                Method method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
378                if (method == null && !getterMethodName.equals(name)) {
379                        getterMethodName = name;
380                        method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
381                }
382                if (method == null) {
383                        throw new IllegalArgumentException(String.format(
384                                        "Could not find getter method '%s' on %s", getterMethodName, safeToString(target)));
385                }
386
387                if (logger.isDebugEnabled()) {
388                        logger.debug(String.format("Invoking getter method '%s' on %s", getterMethodName, safeToString(target)));
389                }
390                ReflectionUtils.makeAccessible(method);
391                return ReflectionUtils.invokeMethod(method, target);
392        }
393
394        /**
395         * Invoke the method with the given {@code name} on the supplied target
396         * object with the supplied arguments.
397         * <p>This method traverses the class hierarchy in search of the desired
398         * method. In addition, an attempt will be made to make non-{@code public}
399         * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
400         * {@code private}, and <em>package-private</em> methods.
401         * @param target the target object on which to invoke the specified method
402         * @param name the name of the method to invoke
403         * @param args the arguments to provide to the method
404         * @return the invocation result, if any
405         * @see MethodInvoker
406         * @see ReflectionUtils#makeAccessible(Method)
407         * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
408         * @see ReflectionUtils#handleReflectionException(Exception)
409         */
410        @SuppressWarnings("unchecked")
411        public static <T> T invokeMethod(Object target, String name, Object... args) {
412                Assert.notNull(target, "Target object must not be null");
413                Assert.hasText(name, "Method name must not be empty");
414
415                try {
416                        MethodInvoker methodInvoker = new MethodInvoker();
417                        methodInvoker.setTargetObject(target);
418                        methodInvoker.setTargetMethod(name);
419                        methodInvoker.setArguments(args);
420                        methodInvoker.prepare();
421
422                        if (logger.isDebugEnabled()) {
423                                logger.debug(String.format("Invoking method '%s' on %s with arguments %s", name, safeToString(target),
424                                                ObjectUtils.nullSafeToString(args)));
425                        }
426
427                        return (T) methodInvoker.invoke();
428                }
429                catch (Exception ex) {
430                        ReflectionUtils.handleReflectionException(ex);
431                        throw new IllegalStateException("Should never get here");
432                }
433        }
434
435        private static String safeToString(Object target) {
436                try {
437                        return String.format("target object [%s]", target);
438                }
439                catch (Exception ex) {
440                        return String.format("target of type [%s] whose toString() method threw [%s]",
441                                (target != null ? target.getClass().getName() : "unknown"), ex);
442                }
443        }
444
445}