c5c0f0469f00a9ad39e630d12e196f5793ff3c58
[jalview.git] / test / junit / extensions / PrivilegedAccessor.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.Locale;
19
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;
31 import java.util.Map;
32 import java.util.StringTokenizer;
33
34 /**
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.
37  * <p>
38  * a.k.a. The "ObjectMolester"
39  * <p>
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>.
43  * 
44  * <pre>
45  * PrivilegedAccessor.invokeMethod(myObject, &quot;setName(java.lang.String)&quot;, &quot;newName&quot;);
46  * </pre>
47  * 
48  * @author Charlie Hubbard (chubbard@iss.net)
49  * @author Prashant Dhokte (pdhokte@iss.net)
50  * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
51  * 
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.
54  */
55 @Deprecated
56 final class PrivilegedAccessor
57 {
58    /**
59     * Private constructor to make it impossible to instantiate this class.
60     */
61    private PrivilegedAccessor() {
62       assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
63    }
64
65    /**
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.
69     * 
70     * @param instanceOrClass the object or class to get a string representation of
71     * @return a string representation of the given object
72     */
73    public static String toString(final Object instanceOrClass) {
74       Collection<String> fields = getFieldNames(instanceOrClass);
75
76       if (fields.isEmpty())
77       {
78         return getClass(instanceOrClass).getName();
79       }
80
81       StringBuffer stringBuffer = new StringBuffer();
82
83       stringBuffer.append(getClass(instanceOrClass).getName() + " {");
84
85       for (String fieldName : fields) {
86          try {
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";
90          }
91       }
92
93       stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}");
94       return stringBuffer.toString();
95    }
96
97    /**
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.
100     * 
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
103     */
104    public static Collection<String> getFieldNames(final Object instanceOrClass) {
105       if (instanceOrClass == null)
106       {
107         return Collections.EMPTY_LIST;
108       }
109
110       Class<?> clazz = getClass(instanceOrClass);
111       Field[] fields = clazz.getDeclaredFields();
112       Collection<String> fieldNames = new ArrayList<String>(fields.length);
113
114       for (Field field : fields) {
115          fieldNames.add(field.getName());
116       }
117       fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
118
119       return fieldNames;
120    }
121
122    /**
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.
125     * 
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
128     */
129    public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
130       if (instanceOrClass == null)
131       {
132         return Collections.EMPTY_LIST;
133       }
134
135       Class<?> clazz = getClass(instanceOrClass);
136       Method[] methods = clazz.getDeclaredMethods();
137       Collection<String> methodSignatures = new ArrayList<String>(methods.length + Object.class.getDeclaredMethods().length);
138
139       for (Method method : methods) {
140          methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")");
141       }
142       methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
143
144       return methodSignatures;
145    }
146
147    /**
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.
149     * 
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
154     */
155    public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException {
156       Field field = getField(instanceOrClass, fieldName);
157       try {
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";
161          return null;
162       }
163    }
164
165    /**
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.
168     * 
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
176     *            inaccessible.
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.
180     * 
181     * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
182     */
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);
186    }
187
188    /**
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.
191     * 
192     * 
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
201     *            inaccessible.
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.
205     * 
206     * @see PrivilegedAccessor#instantiate(Class,Object[])
207     */
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);
212    }
213
214    /**
215     * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
216     * primitives.
217     * 
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
227     */
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(')')))
231       {
232         throw new NoSuchMethodException(methodSignature);
233       }
234       Class<?>[] parameterTypes = getParameterTypes(methodSignature);
235       return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass,
236          getCorrectedArguments(parameterTypes, arguments));
237    }
238
239    /**
240     * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be
241     * mistaken by varargs.
242     * 
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
246     */
247    private static Object[] getCorrectedArguments(Class<?>[] parameterTypes, Object[] arguments) {
248       if (arguments == null)
249       {
250         return arguments;
251       }
252       if (parameterTypes.length > arguments.length)
253       {
254         return arguments;
255       }
256       if (parameterTypes.length < arguments.length)
257       {
258         return getCorrectedArguments(parameterTypes, new Object[] {arguments});
259       }
260
261       Object[] correctedArguments = new Object[arguments.length];
262       int currentArgument = 0;
263       for (Class<?> parameterType : parameterTypes) {
264          correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]);
265          currentArgument++;
266       }
267       return correctedArguments;
268    }
269
270    /**
271     * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be
272     * mistaken by varargs.
273     * 
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
277     */
278    private static Object getCorrectedArgument(Class<?> parameterType, Object argument) {
279       if (!parameterType.isArray() || (argument == null)) {
280          return argument; // normal argument for normal parameterType
281       }
282
283       if (!argument.getClass().isArray()) {
284          return new Object[] {argument};
285       }
286
287       if (parameterType.equals(argument.getClass()))
288        {
289         return argument; // no need to cast
290       }
291
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
298             try {
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 + ".");
303             }
304          }
305       }
306       return correctedArrayArgument;
307    }
308
309    /**
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/>
313     * <br/>
314     * Example:<br/>
315     * <br/>
316     * <code>
317     * String myString = "Test"; <br/>
318     * <br/>
319     * //setting the private field value<br/>
320     * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
321     * <br/>
322     * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
323     * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
324     * <br/>
325     * </code>
326     * 
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
332     */
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);
338       }
339       field.set(instanceOrClass, value);
340    }
341
342    /**
343     * Gets the class with the given className.
344     * 
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
348     */
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();
353       }
354
355       if (className.indexOf("...") > -1) {
356          Class<?> clazz = getClassForName(className.substring(0, className.indexOf("...")));
357          return Array.newInstance(clazz, 0).getClass();
358       }
359
360       try {
361          return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
362       } catch (ClassNotFoundException e) {
363          return getSpecialClassForName(className);
364       }
365    }
366
367    /**
368     * Maps string representation of primitives to their corresponding classes.
369     */
370    private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(8);
371
372    /**
373     * Fills the map with all java primitives and their corresponding classes.
374     */
375    static {
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);
384    }
385
386    /**
387     * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String)
388     * 
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
392     */
393    private static Class<?> getSpecialClassForName(final String className) throws ClassNotFoundException {
394       if (PRIMITIVE_MAPPER.containsKey(className))
395       {
396         return PRIMITIVE_MAPPER.get(className);
397       }
398
399       if (missesPackageName(className))
400       {
401         return getStandardClassForName(className);
402       }
403
404       throw new ClassNotFoundException(className);
405    }
406
407    /**
408     * Gets a 'standard' java class for the given className.
409     * 
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
413     */
414    private static Class<?> getStandardClassForName(String className) throws ClassNotFoundException {
415       try {
416          return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader());
417       } catch (ClassNotFoundException e) {
418          try {
419             return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader());
420          } catch (ClassNotFoundException e1) {
421             throw new ClassNotFoundException(className);
422          }
423       }
424    }
425
426    /**
427     * Tests if the given className possibly misses its package name.
428     * 
429     * @param className the className
430     * @return true if the className might miss its package name, otherwise false
431     */
432    private static boolean missesPackageName(String className) {
433       if (className.contains("."))
434       {
435         return false;
436       }
437       if (className.startsWith(className.substring(0, 1).toUpperCase(Locale.ROOT)))
438       {
439         return true;
440       }
441       return false;
442    }
443
444    /**
445     * Gets the constructor for a given class with the given parameters.
446     * 
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
451     */
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);
455       return constructor;
456    }
457
458    /**
459     * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class.
460     * 
461     * @param instanceOrClass the instance or class to get the field from
462     * @param fieldName the name of the field to get
463     * @return the field
464     * @throws NoSuchFieldException if no such field can be found
465     * @throws InvalidParameterException if instanceOrClass was null
466     */
467    private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException,
468       InvalidParameterException {
469       if (instanceOrClass == null)
470       {
471         throw new InvalidParameterException("Can't get field on null object/class");
472       }
473
474       Class<?> type = getClass(instanceOrClass);
475
476       try {
477          Field field = type.getDeclaredField(fieldName);
478          field.setAccessible(true);
479          return field;
480       } catch (NoSuchFieldException e) {
481          if (type.getSuperclass() == null)
482         {
483           throw e;
484         }
485          return getField(type.getSuperclass(), fieldName);
486       }
487    }
488
489    /**
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
491     * 
492     * @param instanceOrClass the instance or class to get the class of
493     * @return the class of the given parameter
494     */
495    private static Class<?> getClass(final Object instanceOrClass) {
496       if (instanceOrClass instanceof Class)
497       {
498         return (Class<?>) instanceOrClass;
499       }
500
501       return instanceOrClass.getClass();
502    }
503
504    /**
505     * Return the named method with a method signature matching classTypes from the given class.
506     * 
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
510     * @return the method
511     * @throws NoSuchMethodException if the method could not be found
512     */
513    private static Method getMethod(final Class<?> type, final String methodName, final Class<?>[] parameterTypes)
514       throws NoSuchMethodException {
515       try {
516          return type.getDeclaredMethod(methodName, parameterTypes);
517       } catch (NoSuchMethodException e) {
518          if (type.getSuperclass() == null)
519         {
520           throw e;
521         }
522          return getMethod(type.getSuperclass(), methodName, parameterTypes);
523       }
524    }
525
526    /**
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
528     * static method.
529     * 
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
533     * @return the method
534     * @throws NoSuchMethodException if the method could not be found
535     */
536    private static Method getMethod(final Object instanceOrClass, final String methodName, final Class<?>[] parameterTypes)
537       throws NoSuchMethodException {
538       Class<?> type;
539
540       type = getClass(instanceOrClass);
541
542       Method accessMethod = getMethod(type, methodName, parameterTypes);
543       accessMethod.setAccessible(true);
544       return accessMethod;
545    }
546
547    /**
548     * Gets the name of a method.
549     * 
550     * @param methodSignature the signature of the method
551     * @return the name of the method
552     */
553    private static String getMethodName(final String methodSignature) {
554       try {
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";
558          return null;
559       }
560    }
561
562    /**
563     * Gets the types of the parameters.
564     * 
565     * @param parameters the parameters
566     * @return the class-types of the arguments
567     */
568    private static Class<?>[] getParameterTypes(final Object[] parameters) {
569       if (parameters == null)
570       {
571         return new Class[0];
572       }
573
574       Class<?>[] typesOfParameters = new Class[parameters.length];
575
576       for (int i = 0; i < parameters.length; i++) {
577          typesOfParameters[i] = parameters[i].getClass();
578       }
579       return typesOfParameters;
580    }
581
582    /**
583     * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is
584     * thrown.
585     * 
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
590     */
591    private static Class<?>[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException {
592       String signature = getSignatureWithoutBraces(methodSignature);
593
594       StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
595       Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
596
597       for (int x = 0; tokenizer.hasMoreTokens(); x++) {
598          String className = tokenizer.nextToken();
599          try {
600             typesInSignature[x] = getClassForName(className);
601          } catch (ClassNotFoundException e) {
602             NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature);
603             noSuchMethodException.initCause(e);
604             throw noSuchMethodException;
605          }
606       }
607       return typesInSignature;
608    }
609
610    /**
611     * Gets the parameter types as a string.
612     * 
613     * @param classTypes the types to get as names.
614     * @return the parameter types as a string
615     * 
616     * @see java.lang.Class#argumentTypesToString(Class[])
617     */
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)
621       {
622         return "";
623       }
624
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(", ");
629       }
630
631       return parameterTypes.substring(0, parameterTypes.length() - 2);
632    }
633
634    /**
635     * Removes the braces around the methods signature.
636     * 
637     * @param methodSignature the signature with braces
638     * @return the signature without braces
639     */
640    private static String getSignatureWithoutBraces(final String methodSignature) {
641       try {
642          return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')'));
643       } catch (IndexOutOfBoundsException e) {
644          assert false : "signature must have been checked before this method";
645          return null;
646       }
647    }
648
649 }