57c873fc67e6452add0df06998420a150f1734b3
[jalview.git] / test / junit / extensions / PA.java
1 /*
2  * Copyright 2004-2012 Sebastian Dietrich (Sebastian.Dietrich@e-movimento.com)
3  *
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
7  * 
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  * 
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.
15  */
16 package junit.extensions;
17
18 import java.util.Collection;
19
20 /**
21  * 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
22  * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
23  * <p>
24  * a.k.a. The "ObjectMolester"
25  * <p>
26  * Here is an example of using this to access a private member: <br>
27  * Given the following class <code>MyClass</code>: <br>
28  * 
29  * <pre>
30  * public class MyClass {
31  *    private String name; // private attribute
32  * 
33  *    // private constructor
34  *    private MyClass() {
35  *       super();
36  *    }
37  * 
38  *    // private method
39  *    private void setName(String newName) {
40  *       this.name = newName;
41  *    }
42  * }
43  * </pre>
44  * 
45  * We now want to access the class: <br>
46  * 
47  * <pre>
48  * MyClass myObj = PA.instantiate(MyClass.class);
49  * PA.invokeMethod(myObj, &quot;setName(java.lang.String)&quot;, &quot;myNewName&quot;);
50  * String name = PA.getValue(myObj, &quot;name&quot;);
51  * </pre>
52  * 
53  * This class extends {@link PrivilegedAccessor} by re-throwing checked {@link Exception}s as {@link RuntimeException}s.
54  * 
55  * 
56  * @see PrivilegedAccessor
57  * 
58  * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
59  * @author Lubos Bistak (lubos@bistak.sk)
60  */
61 public class PA {
62    private final Object instanceOrClass;
63
64    /**
65     * Private constructor to make it impossible to instantiate this class from outside of PA.
66     * 
67     * @param instanceOrClass
68     */
69    private PA(Object instanceOrClass) {
70       this.instanceOrClass = instanceOrClass;
71    }
72
73    /**
74     * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
75     * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
76     * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
77     * 
78     * @param instanceOrClass the object or class to get a string representation of
79     * @return a string representation of the given object
80     * 
81     * @see PrivilegedAccessor#toString(Object)
82     */
83    public static String toString(final Object instanceOrClass) {
84       return PrivilegedAccessor.toString(instanceOrClass);
85    }
86
87    /**
88     * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
89     * fields (public, private, protected, default) of all its super classes.
90     * 
91     * @param instanceOrClass the instance or class to get the fields of
92     * @return the collection of field names of the given instance or class
93     * 
94     * @see PrivilegedAccessor#getFieldNames(Object)
95     */
96    public static Collection<String> getFieldNames(final Object instanceOrClass) {
97       return PrivilegedAccessor.getFieldNames(instanceOrClass);
98    }
99
100    /**
101     * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
102     * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
103     * 
104     * @param instanceOrClass the instance or class to get the method signatures of
105     * @return the collection of method signatures of the given instance or class
106     * 
107     * @see PrivilegedAccessor#getMethodSignatures(Object)
108     */
109    public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
110       return PrivilegedAccessor.getMethodSignatures(instanceOrClass);
111    }
112
113    /**
114     * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
115     * 
116     * @param instanceOrClass the instance or class to get the field from
117     * @param fieldName the name of the field
118     * @return an object representing the value of the field
119     * @throws IllegalArgumentException if the field does not exist
120     * 
121     * @see PrivilegedAccessor#getValue(Object,String)
122     */
123    public static Object getValue(final Object instanceOrClass, final String fieldName) {
124       try {
125          return PrivilegedAccessor.getValue(instanceOrClass, fieldName);
126       } catch (Exception e) {
127          throw new IllegalArgumentException("Can't get value of " + fieldName + " from " + instanceOrClass, e);
128       }
129    }
130
131    /**
132     * Gets the value of the named field and returns it as an object.
133     * 
134     * @param fieldName the name of the field
135     * @return an object representing the value of the field
136     * @throws IllegalArgumentException if the field does not exist
137     * 
138     * @see PA#getValue(Object,String)
139     */
140    public Object getValue(final String fieldName) {
141       return PA.getValue(instanceOrClass, fieldName);
142    }
143
144    /**
145     * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
146     * member class, you must provide the object it is a member of as first argument.
147     * 
148     * @param fromClass the class to instantiate an object from
149     * @param arguments the arguments to pass to the constructor
150     * @param argumentTypes the fully qualified types of the arguments of the constructor
151     * @return an object of the given type
152     * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
153     *            parameters differ; if an unwrapping conversion for primitive arguments fails; if, after possible unwrapping, a
154     *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
155     *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
156     *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
157     *            underlying constructor represents an abstract class.
158     * 
159     * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
160     */
161    public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object... arguments) {
162       try {
163          return PrivilegedAccessor.instantiate(fromClass, argumentTypes, correctVarargs(arguments));
164       } catch (Exception e) {
165          throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
166       }
167    }
168
169    /**
170     * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
171     * the object it is a member of as first argument.
172     * 
173     * @param fromClass the class to instantiate an object from
174     * @param arguments the arguments to pass to the constructor
175     * @return an object of the given type
176     * @throws IllegalArgumentException if the class can't be instantiated. This could be the case if the number of actual and formal
177     *            parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a
178     *            parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if
179     *            this Constructor object enforces Java language access control and the underlying constructor is inaccessible; if the
180     *            underlying constructor throws an exception; if the constructor could not be found; or if the class that declares the
181     *            underlying constructor represents an abstract class.
182     * 
183     * @see PrivilegedAccessor#instantiate(Class,Object[])
184     */
185    public static <T> T instantiate(final Class<? extends T> fromClass, final Object... arguments) {
186       try {
187          return PrivilegedAccessor.instantiate(fromClass, correctVarargs(arguments));
188       } catch (Exception e) {
189          throw new IllegalArgumentException("Can't instantiate class " + fromClass + " with arguments " + arguments, e);
190       }
191    }
192
193    /**
194     * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
195     * primitives.
196     * 
197     * @param instanceOrClass the instance or class to invoke the method on
198     * @param methodSignature the name of the method and the parameters <br>
199     *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
200     * @param arguments an array of objects to pass as arguments
201     * @return the return value of this method or null if void
202     * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
203     *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
204     *            an argument couldn't be converted to match the expected type
205     * 
206     * @see PrivilegedAccessor#invokeMethod(Object,String,Object[])
207     */
208    public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object... arguments) {
209       try {
210          return PrivilegedAccessor.invokeMethod(instanceOrClass, methodSignature, correctVarargs(arguments));
211       } catch (Exception e) {
212          throw new IllegalArgumentException("Can't invoke method " + methodSignature + " on " + instanceOrClass + " with arguments "
213             + arguments, e);
214       }
215    }
216
217    /**
218     * Calls a method with the given arguments. Arguments can be object types or representations for primitives.
219     * 
220     * @param methodSignature the name of the method and the parameters <br>
221     *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
222     * @param arguments an array of objects to pass as arguments
223     * @return the return value of this method or null if void
224     * @throws IllegalArgumentException if the method could not be invoked. This could be the case if the method is inaccessible; if the
225     *            underlying method throws an exception; if no method with the given <code>methodSignature</code> could be found; or if
226     *            an argument couldn't be converted to match the expected type
227     * @see PA#invokeMethod(Object, String, Object...)
228     */
229    public Object invokeMethod(final String methodSignature, final Object... arguments) {
230       return PA.invokeMethod(instanceOrClass, methodSignature, arguments);
231    }
232
233    /**
234     * Corrects varargs to their initial form. If you call a method with an object-array as last argument the Java varargs mechanism
235     * converts this array in single arguments. This method returns an object array if the arguments are all of the same type.
236     * 
237     * @param arguments the possibly converted arguments of a vararg method
238     * @return arguments possibly converted
239     */
240    private static Object[] correctVarargs(final Object... arguments) {
241       if ((arguments == null) || changedByVararg(arguments)) return new Object[] {arguments};
242       return arguments;
243    }
244
245    /**
246     * Tests if the arguments were changed by vararg. Arguments are changed by vararg if they are of a non primitive array type. E.g.
247     * arguments[] = Object[String[]] is converted to String[] while e.g. arguments[] = Object[int[]] is not converted and stays
248     * Object[int[]]
249     * 
250     * Unfortunately we can't detect the difference for arg = Object[primitive] since arguments[] = Object[Object[primitive]] which is
251     * converted to Object[primitive] and arguments[] = Object[primitive] which stays Object[primitive]
252     * 
253     * and we can't detect the difference for arg = Object[non primitive] since arguments[] = Object[Object[non primitive]] is converted
254     * to Object[non primitive] and arguments[] = Object[non primitive] stays Object[non primitive]
255     * 
256     * @param parameters the parameters
257     * @return true if parameters were changes by varargs, false otherwise
258     */
259    private static boolean changedByVararg(final Object[] parameters) {
260       if ((parameters.length == 0) || (parameters[0] == null)) return false;
261
262       if (parameters.getClass() == Object[].class) return false;
263
264       return true;
265    }
266
267    /**
268     * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
269     * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
270     * at other times than instantiation can have unpredictable effects.<br/>
271     * <br/>
272     * Example:<br/>
273     * <br/>
274     * <code>
275     * String myString = "Test"; <br/>
276     * <br/>
277     * //setting the private field value<br/>
278     * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
279     * <br/>
280     * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
281     * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
282     * <br/>
283     * </code>
284     * 
285     * @param instanceOrClass the instance or class to set the field
286     * @param fieldName the name of the field
287     * @param value the new value of the field
288     * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
289     *            <code>fieldName</code> can be found; or if the field was final
290     * 
291     * @see PrivilegedAccessor.setValue(Object,String,Object)
292     */
293    public static PA setValue(final Object instanceOrClass, final String fieldName, final Object value) {
294       try {
295          PrivilegedAccessor.setValue(instanceOrClass, fieldName, value);
296       } catch (Exception e) {
297          throw new IllegalArgumentException("Can't set value " + value + " at " + fieldName + " in " + instanceOrClass, e);
298       }
299       return new PA(instanceOrClass);
300    }
301
302    /**
303     * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
304     * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
305     * at other times than instantiation can have unpredictable effects.<br/>
306     * <br/>
307     * Example:<br/>
308     * <br/>
309     * <code>
310     * String myString = "Test"; <br/>
311     * <br/>
312     * //setting the private field value<br/>
313     * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
314     * <br/>
315     * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
316     * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
317     * <br/>
318     * </code>
319     * 
320     * @param fieldName the name of the field
321     * @param value the new value of the field
322     * @throws IllegalArgumentException if the value could not be set. This could be the case if no field with the given
323     *            <code>fieldName</code> can be found; or if the field was final
324     * 
325     * @see PA.setValue(Object,String,Object)
326     */
327    public PA setValue(final String fieldName, final Object value) {
328       PA.setValue(instanceOrClass, fieldName, value);
329       return this;
330    }
331 }