2 * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package junit.extensions;
18 import java.util.Locale;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.security.InvalidParameterException;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
32 import java.util.StringTokenizer;
35 * This class is used to access a method or field of an object no matter what the access modifier of the method or field. The syntax
36 * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
38 * a.k.a. The "ObjectMolester"
40 * Here is an example of using this to access a private member: <br>
41 * <code>myObject</code> is an object of type <code>MyClass</code>. <code>setName(String)</code> is a private method of
42 * <code>MyClass</code>.
45 * PrivilegedAccessor.invokeMethod(myObject, "setName(java.lang.String)", "newName");
48 * @author Charlie Hubbard (chubbard@iss.net)
49 * @author Prashant Dhokte (pdhokte@iss.net)
50 * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
52 * @deprecated use PA instead. PA improves the functionality of PrivilegedAccessor by introducing support for varargs and removal of
53 * the necessity to catch exceptions.
56 final class PrivilegedAccessor
59 * Private constructor to make it impossible to instantiate this class.
61 private PrivilegedAccessor() {
62 assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
66 * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
67 * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
68 * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
70 * @param instanceOrClass the object or class to get a string representation of
71 * @return a string representation of the given object
73 public static String toString(final Object instanceOrClass) {
74 Collection<String> fields = getFieldNames(instanceOrClass);
78 return getClass(instanceOrClass).getName();
81 StringBuffer stringBuffer = new StringBuffer();
83 stringBuffer.append(getClass(instanceOrClass).getName() + " {");
85 for (String fieldName : fields) {
87 stringBuffer.append(fieldName + "=" + getValue(instanceOrClass, fieldName) + ", ");
88 } catch (NoSuchFieldException e) {
89 assert false : "It should always be possible to get a field that was just here";
93 stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}");
94 return stringBuffer.toString();
98 * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
99 * fields (public, private, protected, default) of all its super classes.
101 * @param instanceOrClass the instance or class to get the fields of
102 * @return the collection of field names of the given instance or class
104 public static Collection<String> getFieldNames(final Object instanceOrClass) {
105 if (instanceOrClass == null)
107 return Collections.EMPTY_LIST;
110 Class<?> clazz = getClass(instanceOrClass);
111 Field[] fields = clazz.getDeclaredFields();
112 Collection<String> fieldNames = new ArrayList<String>(fields.length);
114 for (Field field : fields) {
115 fieldNames.add(field.getName());
117 fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
123 * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
124 * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
126 * @param instanceOrClass the instance or class to get the method signatures of
127 * @return the collection of method signatures of the given instance or class
129 public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
130 if (instanceOrClass == null)
132 return Collections.EMPTY_LIST;
135 Class<?> clazz = getClass(instanceOrClass);
136 Method[] methods = clazz.getDeclaredMethods();
137 Collection<String> methodSignatures = new ArrayList<String>(methods.length + Object.class.getDeclaredMethods().length);
139 for (Method method : methods) {
140 methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")");
142 methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
144 return methodSignatures;
148 * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
150 * @param instanceOrClass the instance or class to get the field from
151 * @param fieldName the name of the field
152 * @return an object representing the value of the field
153 * @throws NoSuchFieldException if the field does not exist
155 public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException {
156 Field field = getField(instanceOrClass, fieldName);
158 return field.get(instanceOrClass);
159 } catch (IllegalAccessException e) {
160 assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place";
166 * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
167 * the object it is a member of as first argument.
169 * @param fromClass the class to instantiate an object from
170 * @param args the arguments to pass to the constructor
171 * @return an object of the given type
172 * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
173 * arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
174 * parameter type by a method invocation conversion.
175 * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
177 * @throws InvocationTargetException if the underlying constructor throws an exception.
178 * @throws NoSuchMethodException if the constructor could not be found
179 * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
181 * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
183 public static <T> T instantiate(final Class<? extends T> fromClass, final Object[] args) throws IllegalArgumentException,
184 InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
185 return instantiate(fromClass, getParameterTypes(args), args);
189 * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
190 * member class, you must provide the object it is a member of as first argument.
193 * @param fromClass the class to instantiate an object from
194 * @param args the arguments to pass to the constructor
195 * @param argumentTypes the fully qualified types of the arguments of the constructor
196 * @return an object of the given type
197 * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
198 * arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
199 * parameter type by a method invocation conversion.
200 * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
202 * @throws InvocationTargetException if the underlying constructor throws an exception.
203 * @throws NoSuchMethodException if the constructor could not be found
204 * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
206 * @see PrivilegedAccessor#instantiate(Class,Object[])
208 public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object[] args)
209 throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
210 NoSuchMethodException {
211 return getConstructor(fromClass, argumentTypes).newInstance(args);
215 * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
218 * @param instanceOrClass the instance or class to invoke the method on
219 * @param methodSignature the name of the method and the parameters <br>
220 * (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
221 * @param arguments an array of objects to pass as arguments
222 * @return the return value of this method or null if void
223 * @throws IllegalAccessException if the method is inaccessible
224 * @throws InvocationTargetException if the underlying method throws an exception.
225 * @throws NoSuchMethodException if no method with the given <code>methodSignature</code> could be found
226 * @throws IllegalArgumentException if an argument couldn't be converted to match the expected type
228 public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object[] arguments)
229 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
230 if ((methodSignature.indexOf('(') == -1) || (methodSignature.indexOf('(') >= methodSignature.indexOf(')')))
232 throw new NoSuchMethodException(methodSignature);
234 Class<?>[] parameterTypes = getParameterTypes(methodSignature);
235 return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass,
236 getCorrectedArguments(parameterTypes, arguments));
240 * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be
241 * mistaken by varargs.
243 * @param parameterTypes the method signatue the given arguments should match
244 * @param arguments the arguments that should be corrected
245 * @return the corrected arguments
247 private static Object[] getCorrectedArguments(Class<?>[] parameterTypes, Object[] arguments) {
248 if (arguments == null)
252 if (parameterTypes.length > arguments.length)
256 if (parameterTypes.length < arguments.length)
258 return getCorrectedArguments(parameterTypes, new Object[] {arguments});
261 Object[] correctedArguments = new Object[arguments.length];
262 int currentArgument = 0;
263 for (Class<?> parameterType : parameterTypes) {
264 correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]);
267 return correctedArguments;
271 * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be
272 * mistaken by varargs.
274 * @param parameterType the type to match the given argument upon
275 * @param argument the argument to match the given parameterType
276 * @return the corrected argument
278 private static Object getCorrectedArgument(Class<?> parameterType, Object argument) {
279 if (!parameterType.isArray() || (argument == null)) {
280 return argument; // normal argument for normal parameterType
283 if (!argument.getClass().isArray()) {
284 return new Object[] {argument};
287 if (parameterType.equals(argument.getClass()))
289 return argument; // no need to cast
292 // (typed) array argument for (object) array parameterType, elements need to be casted
293 Object correctedArrayArgument = Array.newInstance(parameterType.getComponentType(), Array.getLength(argument));
294 for (int index = 0; index < Array.getLength(argument); index++) {
295 if (parameterType.getComponentType().isPrimitive()) { // rely on autoboxing
296 Array.set(correctedArrayArgument, index, Array.get(argument, index));
297 } else { // cast to expected type
299 Array.set(correctedArrayArgument, index, parameterType.getComponentType().cast(Array.get(argument, index)));
300 } catch (ClassCastException e) {
301 throw new IllegalArgumentException("Argument " + argument + " of type " + argument.getClass()
302 + " does not match expected argument type " + parameterType + ".");
306 return correctedArrayArgument;
310 * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
311 * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
312 * at other times than instantiation can have unpredictable effects.<br/>
317 * String myString = "Test"; <br/>
319 * //setting the private field value<br/>
320 * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
322 * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
323 * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
327 * @param instanceOrClass the instance or class to set the field
328 * @param fieldName the name of the field
329 * @param value the new value of the field
330 * @throws NoSuchFieldException if no field with the given <code>fieldName</code> can be found
331 * @throws IllegalAccessException possibly if the field was final
333 public static void setValue(final Object instanceOrClass, final String fieldName, final Object value) throws NoSuchFieldException,
334 IllegalAccessException {
335 Field field = getField(instanceOrClass, fieldName);
336 if (Modifier.isFinal(field.getModifiers())) {
337 PrivilegedAccessor.setValue(field, "modifiers", field.getModifiers() ^ Modifier.FINAL);
339 field.set(instanceOrClass, value);
343 * Gets the class with the given className.
345 * @param className the name of the class to get
346 * @return the class for the given className
347 * @throws ClassNotFoundException if the class could not be found
349 private static Class<?> getClassForName(final String className) throws ClassNotFoundException {
350 if (className.indexOf('[') > -1) {
351 Class<?> clazz = getClassForName(className.substring(0, className.indexOf('[')));
352 return Array.newInstance(clazz, 0).getClass();
355 if (className.indexOf("...") > -1) {
356 Class<?> clazz = getClassForName(className.substring(0, className.indexOf("...")));
357 return Array.newInstance(clazz, 0).getClass();
361 return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
362 } catch (ClassNotFoundException e) {
363 return getSpecialClassForName(className);
368 * Maps string representation of primitives to their corresponding classes.
370 private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(8);
373 * Fills the map with all java primitives and their corresponding classes.
376 PRIMITIVE_MAPPER.put("int", Integer.TYPE);
377 PRIMITIVE_MAPPER.put("float", Float.TYPE);
378 PRIMITIVE_MAPPER.put("double", Double.TYPE);
379 PRIMITIVE_MAPPER.put("short", Short.TYPE);
380 PRIMITIVE_MAPPER.put("long", Long.TYPE);
381 PRIMITIVE_MAPPER.put("byte", Byte.TYPE);
382 PRIMITIVE_MAPPER.put("char", Character.TYPE);
383 PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE);
387 * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String)
389 * @param className the name of the class to get
390 * @return the class for the given className
391 * @throws ClassNotFoundException if the class could not be found
393 private static Class<?> getSpecialClassForName(final String className) throws ClassNotFoundException {
394 if (PRIMITIVE_MAPPER.containsKey(className))
396 return PRIMITIVE_MAPPER.get(className);
399 if (missesPackageName(className))
401 return getStandardClassForName(className);
404 throw new ClassNotFoundException(className);
408 * Gets a 'standard' java class for the given className.
410 * @param className the className
411 * @return the class for the given className (if any)
412 * @throws ClassNotFoundException of no 'standard' java class was found for the given className
414 private static Class<?> getStandardClassForName(String className) throws ClassNotFoundException {
416 return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader());
417 } catch (ClassNotFoundException e) {
419 return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader());
420 } catch (ClassNotFoundException e1) {
421 throw new ClassNotFoundException(className);
427 * Tests if the given className possibly misses its package name.
429 * @param className the className
430 * @return true if the className might miss its package name, otherwise false
432 private static boolean missesPackageName(String className) {
433 if (className.contains("."))
437 if (className.startsWith(className.substring(0, 1).toUpperCase(Locale.ROOT)))
445 * Gets the constructor for a given class with the given parameters.
447 * @param type the class to instantiate
448 * @param parameterTypes the types of the parameters
449 * @return the constructor
450 * @throws NoSuchMethodException if the method could not be found
452 private static <T> Constructor<T> getConstructor(final Class<T> type, final Class<?>[] parameterTypes) throws NoSuchMethodException {
453 Constructor<T> constructor = type.getDeclaredConstructor(parameterTypes);
454 constructor.setAccessible(true);
459 * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class.
461 * @param instanceOrClass the instance or class to get the field from
462 * @param fieldName the name of the field to get
464 * @throws NoSuchFieldException if no such field can be found
465 * @throws InvalidParameterException if instanceOrClass was null
467 private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException,
468 InvalidParameterException {
469 if (instanceOrClass == null)
471 throw new InvalidParameterException("Can't get field on null object/class");
474 Class<?> type = getClass(instanceOrClass);
477 Field field = type.getDeclaredField(fieldName);
478 field.setAccessible(true);
480 } catch (NoSuchFieldException e) {
481 if (type.getSuperclass() == null)
485 return getField(type.getSuperclass(), fieldName);
490 * Gets the class of the given parameter. If the parameter is a class, it is returned, if it is an object, its class is returned
492 * @param instanceOrClass the instance or class to get the class of
493 * @return the class of the given parameter
495 private static Class<?> getClass(final Object instanceOrClass) {
496 if (instanceOrClass instanceof Class)
498 return (Class<?>) instanceOrClass;
501 return instanceOrClass.getClass();
505 * Return the named method with a method signature matching classTypes from the given class.
507 * @param type the class to get the method from
508 * @param methodName the name of the method to get
509 * @param parameterTypes the parameter-types of the method to get
511 * @throws NoSuchMethodException if the method could not be found
513 private static Method getMethod(final Class<?> type, final String methodName, final Class<?>[] parameterTypes)
514 throws NoSuchMethodException {
516 return type.getDeclaredMethod(methodName, parameterTypes);
517 } catch (NoSuchMethodException e) {
518 if (type.getSuperclass() == null)
522 return getMethod(type.getSuperclass(), methodName, parameterTypes);
527 * Gets the method with the given name and parameters from the given instance or class. If instanceOrClass is a class, then we get a
530 * @param instanceOrClass the instance or class to get the method of
531 * @param methodName the name of the method
532 * @param parameterTypes the parameter-types of the method to get
534 * @throws NoSuchMethodException if the method could not be found
536 private static Method getMethod(final Object instanceOrClass, final String methodName, final Class<?>[] parameterTypes)
537 throws NoSuchMethodException {
540 type = getClass(instanceOrClass);
542 Method accessMethod = getMethod(type, methodName, parameterTypes);
543 accessMethod.setAccessible(true);
548 * Gets the name of a method.
550 * @param methodSignature the signature of the method
551 * @return the name of the method
553 private static String getMethodName(final String methodSignature) {
555 return methodSignature.substring(0, methodSignature.indexOf('(')).trim();
556 } catch (StringIndexOutOfBoundsException e) {
557 assert false : "Signature must have been checked before this method was called";
563 * Gets the types of the parameters.
565 * @param parameters the parameters
566 * @return the class-types of the arguments
568 private static Class<?>[] getParameterTypes(final Object[] parameters) {
569 if (parameters == null)
574 Class<?>[] typesOfParameters = new Class[parameters.length];
576 for (int i = 0; i < parameters.length; i++) {
577 typesOfParameters[i] = parameters[i].getClass();
579 return typesOfParameters;
583 * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is
586 * @param methodSignature the signature of the method
587 * @return the parameter types as class[]
588 * @throws NoSuchMethodException if the method could not be found
589 * @throws IllegalArgumentException if one of the given parameters doesn't math the given methodSignature
591 private static Class<?>[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException {
592 String signature = getSignatureWithoutBraces(methodSignature);
594 StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
595 Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
597 for (int x = 0; tokenizer.hasMoreTokens(); x++) {
598 String className = tokenizer.nextToken();
600 typesInSignature[x] = getClassForName(className);
601 } catch (ClassNotFoundException e) {
602 NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature);
603 noSuchMethodException.initCause(e);
604 throw noSuchMethodException;
607 return typesInSignature;
611 * Gets the parameter types as a string.
613 * @param classTypes the types to get as names.
614 * @return the parameter types as a string
616 * @see java.lang.Class#argumentTypesToString(Class[])
618 private static String getParameterTypesAsString(final Class<?>[] classTypes) {
619 assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
620 if (classTypes.length == 0)
625 StringBuilder parameterTypes = new StringBuilder();
626 for (Class<?> clazz : classTypes) {
627 assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
628 parameterTypes.append(clazz.getName()).append(", ");
631 return parameterTypes.substring(0, parameterTypes.length() - 2);
635 * Removes the braces around the methods signature.
637 * @param methodSignature the signature with braces
638 * @return the signature without braces
640 private static String getSignatureWithoutBraces(final String methodSignature) {
642 return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')'));
643 } catch (IndexOutOfBoundsException e) {
644 assert false : "signature must have been checked before this method";