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