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
36 * the access modifier of the method or field. The syntax for accessing fields
37 * and methods is out of the ordinary because this class uses reflection to peel
40 * a.k.a. The "ObjectMolester"
42 * Here is an example of using this to access a private member: <br>
43 * <code>myObject</code> is an object of type <code>MyClass</code>.
44 * <code>setName(String)</code> is a private method of <code>MyClass</code>.
47 * PrivilegedAccessor.invokeMethod(myObject, "setName(java.lang.String)",
48 * "newName");
51 * @author Charlie Hubbard (chubbard@iss.net)
52 * @author Prashant Dhokte (pdhokte@iss.net)
53 * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
55 * @deprecated use PA instead. PA improves the functionality of
56 * PrivilegedAccessor by introducing support for varargs and removal
57 * of the necessity to catch exceptions.
60 final class PrivilegedAccessor
63 * Private constructor to make it impossible to instantiate this class.
65 private PrivilegedAccessor()
67 assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
71 * Returns a string representation of the given object. The string has the
72 * following format: "<classname> {<attributes and values>}" whereas
73 * <attributes and values> is a comma separated list with
74 * <attributeName>=<attributeValue> <atributes and values> includes all
75 * attributes of the objects class followed by the attributes of its
76 * superclass (if any) and so on.
78 * @param instanceOrClass
79 * the object or class to get a string representation of
80 * @return a string representation of the given object
82 public static String toString(final Object instanceOrClass)
84 Collection<String> fields = getFieldNames(instanceOrClass);
88 return getClass(instanceOrClass).getName();
91 StringBuffer stringBuffer = new StringBuffer();
93 stringBuffer.append(getClass(instanceOrClass).getName() + " {");
95 for (String fieldName : fields)
99 stringBuffer.append(fieldName + "="
100 + getValue(instanceOrClass, fieldName) + ", ");
101 } catch (NoSuchFieldException e)
103 assert false : "It should always be possible to get a field that was just here";
107 stringBuffer.replace(stringBuffer.lastIndexOf(", "),
108 stringBuffer.length(), "}");
109 return stringBuffer.toString();
113 * Gets the name of all fields (public, private, protected, default) of the
114 * given instance or class. This includes as well all fields (public, private,
115 * protected, default) of all its super classes.
117 * @param instanceOrClass
118 * the instance or class to get the fields of
119 * @return the collection of field names of the given instance or class
121 public static Collection<String> getFieldNames(
122 final Object instanceOrClass)
124 if (instanceOrClass == null)
126 return Collections.EMPTY_LIST;
129 Class<?> clazz = getClass(instanceOrClass);
130 Field[] fields = clazz.getDeclaredFields();
131 Collection<String> fieldNames = new ArrayList<String>(fields.length);
133 for (Field field : fields)
135 fieldNames.add(field.getName());
137 fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
143 * Gets the signatures of all methods (public, private, protected, default) of
144 * the given instance or class. This includes as well all methods (public,
145 * private, protected, default) of all its super classes. This does not
146 * include constructors.
148 * @param instanceOrClass
149 * the instance or class to get the method signatures of
150 * @return the collection of method signatures of the given instance or class
152 public static Collection<String> getMethodSignatures(
153 final Object instanceOrClass)
155 if (instanceOrClass == null)
157 return Collections.EMPTY_LIST;
160 Class<?> clazz = getClass(instanceOrClass);
161 Method[] methods = clazz.getDeclaredMethods();
162 Collection<String> methodSignatures = new ArrayList<String>(
163 methods.length + Object.class.getDeclaredMethods().length);
165 for (Method method : methods)
167 methodSignatures.add(method.getName() + "("
168 + getParameterTypesAsString(method.getParameterTypes())
171 methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
173 return methodSignatures;
177 * Gets the value of the named field and returns it as an object. If
178 * instanceOrClass is a class then a static field is returned.
180 * @param instanceOrClass
181 * the instance or class to get the field from
183 * the name of the field
184 * @return an object representing the value of the field
185 * @throws NoSuchFieldException
186 * if the field does not exist
188 public static Object getValue(final Object instanceOrClass,
189 final String fieldName) throws NoSuchFieldException
191 Field field = getField(instanceOrClass, fieldName);
194 return field.get(instanceOrClass);
195 } catch (IllegalAccessException e)
197 assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place";
203 * Instantiates an object of the given class with the given arguments. If you
204 * want to instantiate a member class, you must provide the object it is a
205 * member of as first argument.
208 * the class to instantiate an object from
210 * the arguments to pass to the constructor
211 * @return an object of the given type
212 * @throws IllegalArgumentException
213 * if the number of actual and formal parameters differ; if an
214 * unwrapping conversion for primitive arguments fails; or if, after
215 * possible unwrapping, a parameter value cannot be converted to the
216 * corresponding formal parameter type by a method invocation
218 * @throws IllegalAccessException
219 * if this Constructor object enforces Java language access control
220 * and the underlying constructor is inaccessible.
221 * @throws InvocationTargetException
222 * if the underlying constructor throws an exception.
223 * @throws NoSuchMethodException
224 * if the constructor could not be found
225 * @throws InstantiationException
226 * if the class that declares the underlying constructor represents
229 * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
231 public static <T> T instantiate(final Class<? extends T> fromClass,
232 final Object[] args) throws IllegalArgumentException,
233 InstantiationException, IllegalAccessException,
234 InvocationTargetException, NoSuchMethodException
236 return instantiate(fromClass, getParameterTypes(args), args);
240 * Instantiates an object of the given class with the given arguments and the
241 * given argument types. If you want to instantiate a member class, you must
242 * provide the object it is a member of as first argument.
246 * the class to instantiate an object from
248 * the arguments to pass to the constructor
249 * @param argumentTypes
250 * the fully qualified types of the arguments of the constructor
251 * @return an object of the given type
252 * @throws IllegalArgumentException
253 * if the number of actual and formal parameters differ; if an
254 * unwrapping conversion for primitive arguments fails; or if, after
255 * possible unwrapping, a parameter value cannot be converted to the
256 * corresponding formal parameter type by a method invocation
258 * @throws IllegalAccessException
259 * if this Constructor object enforces Java language access control
260 * and the underlying constructor is inaccessible.
261 * @throws InvocationTargetException
262 * if the underlying constructor throws an exception.
263 * @throws NoSuchMethodException
264 * if the constructor could not be found
265 * @throws InstantiationException
266 * if the class that declares the underlying constructor represents
269 * @see PrivilegedAccessor#instantiate(Class,Object[])
271 public static <T> T instantiate(final Class<? extends T> fromClass,
272 final Class<?>[] argumentTypes, final Object[] args)
273 throws IllegalArgumentException, InstantiationException,
274 IllegalAccessException, InvocationTargetException,
275 NoSuchMethodException
277 return getConstructor(fromClass, argumentTypes).newInstance(args);
281 * Calls a method on the given object instance with the given arguments.
282 * Arguments can be object types or representations for primitives.
284 * @param instanceOrClass
285 * the instance or class to invoke the method on
286 * @param methodSignature
287 * the name of the method and the parameters <br>
288 * (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
290 * an array of objects to pass as arguments
291 * @return the return value of this method or null if void
292 * @throws IllegalAccessException
293 * if the method is inaccessible
294 * @throws InvocationTargetException
295 * if the underlying method throws an exception.
296 * @throws NoSuchMethodException
297 * if no method with the given <code>methodSignature</code> could be
299 * @throws IllegalArgumentException
300 * if an argument couldn't be converted to match the expected type
302 public static Object invokeMethod(final Object instanceOrClass,
303 final String methodSignature, final Object[] arguments)
304 throws IllegalArgumentException, IllegalAccessException,
305 InvocationTargetException, NoSuchMethodException
307 if ((methodSignature.indexOf('(') == -1) || (methodSignature
308 .indexOf('(') >= methodSignature.indexOf(')')))
310 throw new NoSuchMethodException(methodSignature);
312 Class<?>[] parameterTypes = getParameterTypes(methodSignature);
313 return getMethod(instanceOrClass, getMethodName(methodSignature),
314 parameterTypes).invoke(instanceOrClass,
315 getCorrectedArguments(parameterTypes, arguments));
319 * Gets the given arguments corrected to match the given methodSignature.
320 * Correction is necessary for array arguments not to be mistaken by varargs.
322 * @param parameterTypes
323 * the method signatue the given arguments should match
325 * the arguments that should be corrected
326 * @return the corrected arguments
328 private static Object[] getCorrectedArguments(Class<?>[] parameterTypes,
331 if (arguments == null)
335 if (parameterTypes.length > arguments.length)
339 if (parameterTypes.length < arguments.length)
341 return getCorrectedArguments(parameterTypes,
346 Object[] correctedArguments = new Object[arguments.length];
347 int currentArgument = 0;
348 for (Class<?> parameterType : parameterTypes)
350 correctedArguments[currentArgument] = getCorrectedArgument(
351 parameterType, arguments[currentArgument]);
354 return correctedArguments;
358 * Gets the given argument corrected to match the given parameterType.
359 * Correction is necessary for array arguments not to be mistaken by varargs.
361 * @param parameterType
362 * the type to match the given argument upon
364 * the argument to match the given parameterType
365 * @return the corrected argument
367 private static Object getCorrectedArgument(Class<?> parameterType,
370 if (!parameterType.isArray() || (argument == null))
372 return argument; // normal argument for normal parameterType
375 if (!argument.getClass().isArray())
377 return new Object[] { argument };
380 if (parameterType.equals(argument.getClass()))
382 return argument; // no need to cast
385 // (typed) array argument for (object) array parameterType, elements need to
387 Object correctedArrayArgument = Array.newInstance(
388 parameterType.getComponentType(), Array.getLength(argument));
389 for (int index = 0; index < Array.getLength(argument); index++)
391 if (parameterType.getComponentType().isPrimitive())
392 { // rely on autoboxing
393 Array.set(correctedArrayArgument, index,
394 Array.get(argument, index));
397 { // cast to expected type
400 Array.set(correctedArrayArgument, index, parameterType
401 .getComponentType().cast(Array.get(argument, index)));
402 } catch (ClassCastException e)
404 throw new IllegalArgumentException(
405 "Argument " + argument + " of type " + argument.getClass()
406 + " does not match expected argument type "
407 + parameterType + ".");
411 return correctedArrayArgument;
415 * Sets the value of the named field. If fieldName denotes a static field,
416 * provide a class, otherwise provide an instance. If the fieldName denotes a
417 * final field, this method could fail with an IllegalAccessException, since
418 * setting the value of final fields at other times than instantiation can
419 * have unpredictable effects.<br/>
424 * String myString = "Test"; <br/>
426 * //setting the private field value<br/>
427 * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
429 * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
430 * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
434 * @param instanceOrClass
435 * the instance or class to set the field
437 * the name of the field
439 * the new value of the field
440 * @throws NoSuchFieldException
441 * if no field with the given <code>fieldName</code> can be found
442 * @throws IllegalAccessException
443 * possibly if the field was final
445 public static void setValue(final Object instanceOrClass,
446 final String fieldName, final Object value)
447 throws NoSuchFieldException, IllegalAccessException
449 Field field = getField(instanceOrClass, fieldName);
450 if (Modifier.isFinal(field.getModifiers()))
452 PrivilegedAccessor.setValue(field, "modifiers",
453 field.getModifiers() ^ Modifier.FINAL);
455 field.set(instanceOrClass, value);
459 * Gets the class with the given className.
462 * the name of the class to get
463 * @return the class for the given className
464 * @throws ClassNotFoundException
465 * if the class could not be found
467 private static Class<?> getClassForName(final String className)
468 throws ClassNotFoundException
470 if (className.indexOf('[') > -1)
472 Class<?> clazz = getClassForName(
473 className.substring(0, className.indexOf('[')));
474 return Array.newInstance(clazz, 0).getClass();
477 if (className.indexOf("...") > -1)
479 Class<?> clazz = getClassForName(
480 className.substring(0, className.indexOf("...")));
481 return Array.newInstance(clazz, 0).getClass();
486 return Class.forName(className, false,
487 Thread.currentThread().getContextClassLoader());
488 } catch (ClassNotFoundException e)
490 return getSpecialClassForName(className);
495 * Maps string representation of primitives to their corresponding classes.
497 private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(
501 * Fills the map with all java primitives and their corresponding classes.
505 PRIMITIVE_MAPPER.put("int", Integer.TYPE);
506 PRIMITIVE_MAPPER.put("float", Float.TYPE);
507 PRIMITIVE_MAPPER.put("double", Double.TYPE);
508 PRIMITIVE_MAPPER.put("short", Short.TYPE);
509 PRIMITIVE_MAPPER.put("long", Long.TYPE);
510 PRIMITIVE_MAPPER.put("byte", Byte.TYPE);
511 PRIMITIVE_MAPPER.put("char", Character.TYPE);
512 PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE);
516 * Gets special classes for the given className. Special classes are
517 * primitives and "standard" Java types (like String)
520 * the name of the class to get
521 * @return the class for the given className
522 * @throws ClassNotFoundException
523 * if the class could not be found
525 private static Class<?> getSpecialClassForName(final String className)
526 throws ClassNotFoundException
528 if (PRIMITIVE_MAPPER.containsKey(className))
530 return PRIMITIVE_MAPPER.get(className);
533 if (missesPackageName(className))
535 return getStandardClassForName(className);
538 throw new ClassNotFoundException(className);
542 * Gets a 'standard' java class for the given className.
546 * @return the class for the given className (if any)
547 * @throws ClassNotFoundException
548 * of no 'standard' java class was found for the given className
550 private static Class<?> getStandardClassForName(String className)
551 throws ClassNotFoundException
555 return Class.forName("java.lang." + className, false,
556 Thread.currentThread().getContextClassLoader());
557 } catch (ClassNotFoundException e)
561 return Class.forName("java.util." + className, false,
562 Thread.currentThread().getContextClassLoader());
563 } catch (ClassNotFoundException e1)
565 throw new ClassNotFoundException(className);
571 * Tests if the given className possibly misses its package name.
575 * @return true if the className might miss its package name, otherwise false
577 private static boolean missesPackageName(String className)
579 if (className.contains("."))
584 .startsWith(className.substring(0, 1).toUpperCase(Locale.ROOT)))
592 * Gets the constructor for a given class with the given parameters.
595 * the class to instantiate
596 * @param parameterTypes
597 * the types of the parameters
598 * @return the constructor
599 * @throws NoSuchMethodException
600 * if the method could not be found
602 private static <T> Constructor<T> getConstructor(final Class<T> type,
603 final Class<?>[] parameterTypes) throws NoSuchMethodException
605 Constructor<T> constructor = type
606 .getDeclaredConstructor(parameterTypes);
607 constructor.setAccessible(true);
612 * Return the named field from the given instance or class. Returns a static
613 * field if instanceOrClass is a class.
615 * @param instanceOrClass
616 * the instance or class to get the field from
618 * the name of the field to get
620 * @throws NoSuchFieldException
621 * if no such field can be found
622 * @throws InvalidParameterException
623 * if instanceOrClass was null
625 private static Field getField(final Object instanceOrClass,
626 final String fieldName)
627 throws NoSuchFieldException, InvalidParameterException
629 if (instanceOrClass == null)
631 throw new InvalidParameterException(
632 "Can't get field on null object/class");
635 Class<?> type = getClass(instanceOrClass);
639 Field field = type.getDeclaredField(fieldName);
640 field.setAccessible(true);
642 } catch (NoSuchFieldException e)
644 if (type.getSuperclass() == null)
648 return getField(type.getSuperclass(), fieldName);
653 * Gets the class of the given parameter. If the parameter is a class, it is
654 * returned, if it is an object, its class is returned
656 * @param instanceOrClass
657 * the instance or class to get the class of
658 * @return the class of the given parameter
660 private static Class<?> getClass(final Object instanceOrClass)
662 if (instanceOrClass instanceof Class)
664 return (Class<?>) instanceOrClass;
667 return instanceOrClass.getClass();
671 * Return the named method with a method signature matching classTypes from
675 * the class to get the method from
677 * the name of the method to get
678 * @param parameterTypes
679 * the parameter-types of the method to get
681 * @throws NoSuchMethodException
682 * if the method could not be found
684 private static Method getMethod(final Class<?> type,
685 final String methodName, final Class<?>[] parameterTypes)
686 throws NoSuchMethodException
690 return type.getDeclaredMethod(methodName, parameterTypes);
691 } catch (NoSuchMethodException e)
693 if (type.getSuperclass() == null)
697 return getMethod(type.getSuperclass(), methodName, parameterTypes);
702 * Gets the method with the given name and parameters from the given instance
703 * or class. If instanceOrClass is a class, then we get a static method.
705 * @param instanceOrClass
706 * the instance or class to get the method of
708 * the name of the method
709 * @param parameterTypes
710 * the parameter-types of the method to get
712 * @throws NoSuchMethodException
713 * if the method could not be found
715 private static Method getMethod(final Object instanceOrClass,
716 final String methodName, final Class<?>[] parameterTypes)
717 throws NoSuchMethodException
721 type = getClass(instanceOrClass);
723 Method accessMethod = getMethod(type, methodName, parameterTypes);
724 accessMethod.setAccessible(true);
729 * Gets the name of a method.
731 * @param methodSignature
732 * the signature of the method
733 * @return the name of the method
735 private static String getMethodName(final String methodSignature)
739 return methodSignature.substring(0, methodSignature.indexOf('('))
741 } catch (StringIndexOutOfBoundsException e)
743 assert false : "Signature must have been checked before this method was called";
749 * Gets the types of the parameters.
753 * @return the class-types of the arguments
755 private static Class<?>[] getParameterTypes(final Object[] parameters)
757 if (parameters == null)
762 Class<?>[] typesOfParameters = new Class[parameters.length];
764 for (int i = 0; i < parameters.length; i++)
766 typesOfParameters[i] = parameters[i].getClass();
768 return typesOfParameters;
772 * Gets the types of the given parameters. If the parameters don't match the
773 * given methodSignature an IllegalArgumentException is thrown.
775 * @param methodSignature
776 * the signature of the method
777 * @return the parameter types as class[]
778 * @throws NoSuchMethodException
779 * if the method could not be found
780 * @throws IllegalArgumentException
781 * if one of the given parameters doesn't math the given
784 private static Class<?>[] getParameterTypes(final String methodSignature)
785 throws NoSuchMethodException, IllegalArgumentException
787 String signature = getSignatureWithoutBraces(methodSignature);
789 StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
790 Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
792 for (int x = 0; tokenizer.hasMoreTokens(); x++)
794 String className = tokenizer.nextToken();
797 typesInSignature[x] = getClassForName(className);
798 } catch (ClassNotFoundException e)
800 NoSuchMethodException noSuchMethodException = new NoSuchMethodException(
802 noSuchMethodException.initCause(e);
803 throw noSuchMethodException;
806 return typesInSignature;
810 * Gets the parameter types as a string.
813 * the types to get as names.
814 * @return the parameter types as a string
816 * @see java.lang.Class#argumentTypesToString(Class[])
818 private static String getParameterTypesAsString(
819 final Class<?>[] classTypes)
821 assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
822 if (classTypes.length == 0)
827 StringBuilder parameterTypes = new StringBuilder();
828 for (Class<?> clazz : classTypes)
830 assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
831 parameterTypes.append(clazz.getName()).append(", ");
834 return parameterTypes.substring(0, parameterTypes.length() - 2);
838 * Removes the braces around the methods signature.
840 * @param methodSignature
841 * the signature with braces
842 * @return the signature without braces
844 private static String getSignatureWithoutBraces(
845 final String methodSignature)
849 return methodSignature.substring(methodSignature.indexOf('(') + 1,
850 methodSignature.indexOf(')'));
851 } catch (IndexOutOfBoundsException e)
853 assert false : "signature must have been checked before this method";