Merge branch 'Jalview-JS/jim/JAL-3253-JAL-3418' into Jalview-JS/JAL-3253-applet
[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.lang.reflect.Array;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.security.InvalidParameterException;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.StringTokenizer;
31
32 /**
33  * 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
34  * for accessing fields and methods is out of the ordinary because this class uses reflection to peel away protection.
35  * <p>
36  * a.k.a. The "ObjectMolester"
37  * <p>
38  * Here is an example of using this to access a private member: <br>
39  * <code>myObject</code> is an object of type <code>MyClass</code>. <code>setName(String)</code> is a private method of
40  * <code>MyClass</code>.
41  * 
42  * <pre>
43  * PrivilegedAccessor.invokeMethod(myObject, &quot;setName(java.lang.String)&quot;, &quot;newName&quot;);
44  * </pre>
45  * 
46  * @author Charlie Hubbard (chubbard@iss.net)
47  * @author Prashant Dhokte (pdhokte@iss.net)
48  * @author Sebastian Dietrich (sebastian.dietrich@e-movimento.com)
49  * 
50  * @deprecated use PA instead. PA improves the functionality of PrivilegedAccessor by introducing support for varargs and removal of
51  *             the necessity to catch exceptions.
52  */
53 @Deprecated
54 final class PrivilegedAccessor
55 {
56    /**
57     * Private constructor to make it impossible to instantiate this class.
58     */
59    private PrivilegedAccessor() {
60       assert false : "You mustn't instantiate PrivilegedAccessor, use its methods statically";
61    }
62
63    /**
64     * Returns a string representation of the given object. The string has the following format: "<classname> {<attributes and values>}"
65     * whereas <attributes and values> is a comma separated list with <attributeName>=<attributeValue> <atributes and values> includes
66     * all attributes of the objects class followed by the attributes of its superclass (if any) and so on.
67     * 
68     * @param instanceOrClass the object or class to get a string representation of
69     * @return a string representation of the given object
70     */
71    public static String toString(final Object instanceOrClass) {
72       Collection<String> fields = getFieldNames(instanceOrClass);
73
74       if (fields.isEmpty())
75       {
76         return getClass(instanceOrClass).getName();
77       }
78
79       StringBuffer stringBuffer = new StringBuffer();
80
81       stringBuffer.append(getClass(instanceOrClass).getName() + " {");
82
83       for (String fieldName : fields) {
84          try {
85             stringBuffer.append(fieldName + "=" + getValue(instanceOrClass, fieldName) + ", ");
86          } catch (NoSuchFieldException e) {
87             assert false : "It should always be possible to get a field that was just here";
88          }
89       }
90
91       stringBuffer.replace(stringBuffer.lastIndexOf(", "), stringBuffer.length(), "}");
92       return stringBuffer.toString();
93    }
94
95    /**
96     * Gets the name of all fields (public, private, protected, default) of the given instance or class. This includes as well all
97     * fields (public, private, protected, default) of all its super classes.
98     * 
99     * @param instanceOrClass the instance or class to get the fields of
100     * @return the collection of field names of the given instance or class
101     */
102    public static Collection<String> getFieldNames(final Object instanceOrClass) {
103       if (instanceOrClass == null)
104       {
105         return Collections.EMPTY_LIST;
106       }
107
108       Class<?> clazz = getClass(instanceOrClass);
109       Field[] fields = clazz.getDeclaredFields();
110       Collection<String> fieldNames = new ArrayList<String>(fields.length);
111
112       for (Field field : fields) {
113          fieldNames.add(field.getName());
114       }
115       fieldNames.addAll(getFieldNames(clazz.getSuperclass()));
116
117       return fieldNames;
118    }
119
120    /**
121     * Gets the signatures of all methods (public, private, protected, default) of the given instance or class. This includes as well
122     * all methods (public, private, protected, default) of all its super classes. This does not include constructors.
123     * 
124     * @param instanceOrClass the instance or class to get the method signatures of
125     * @return the collection of method signatures of the given instance or class
126     */
127    public static Collection<String> getMethodSignatures(final Object instanceOrClass) {
128       if (instanceOrClass == null)
129       {
130         return Collections.EMPTY_LIST;
131       }
132
133       Class<?> clazz = getClass(instanceOrClass);
134       Method[] methods = clazz.getDeclaredMethods();
135       Collection<String> methodSignatures = new ArrayList<String>(methods.length + Object.class.getDeclaredMethods().length);
136
137       for (Method method : methods) {
138          methodSignatures.add(method.getName() + "(" + getParameterTypesAsString(method.getParameterTypes()) + ")");
139       }
140       methodSignatures.addAll(getMethodSignatures(clazz.getSuperclass()));
141
142       return methodSignatures;
143    }
144
145    /**
146     * Gets the value of the named field and returns it as an object. If instanceOrClass is a class then a static field is returned.
147     * 
148     * @param instanceOrClass the instance or class to get the field from
149     * @param fieldName the name of the field
150     * @return an object representing the value of the field
151     * @throws NoSuchFieldException if the field does not exist
152     */
153    public static Object getValue(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException {
154       Field field = getField(instanceOrClass, fieldName);
155       try {
156          return field.get(instanceOrClass);
157       } catch (IllegalAccessException e) {
158          assert false : "getField() should have setAccessible(true), so an IllegalAccessException should not occur in this place";
159          return null;
160       }
161    }
162
163    /**
164     * Instantiates an object of the given class with the given arguments. If you want to instantiate a member class, you must provide
165     * the object it is a member of as first argument.
166     * 
167     * @param fromClass the class to instantiate an object from
168     * @param args the arguments to pass to the constructor
169     * @return an object of the given type
170     * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
171     *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
172     *            parameter type by a method invocation conversion.
173     * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
174     *            inaccessible.
175     * @throws InvocationTargetException if the underlying constructor throws an exception.
176     * @throws NoSuchMethodException if the constructor could not be found
177     * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
178     * 
179     * @see PrivilegedAccessor#instantiate(Class,Class[],Object[])
180     */
181    public static <T> T instantiate(final Class<? extends T> fromClass, final Object[] args) throws IllegalArgumentException,
182       InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
183       return instantiate(fromClass, getParameterTypes(args), args);
184    }
185
186    /**
187     * Instantiates an object of the given class with the given arguments and the given argument types. If you want to instantiate a
188     * member class, you must provide the object it is a member of as first argument.
189     * 
190     * 
191     * @param fromClass the class to instantiate an object from
192     * @param args the arguments to pass to the constructor
193     * @param argumentTypes the fully qualified types of the arguments of the constructor
194     * @return an object of the given type
195     * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive
196     *            arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal
197     *            parameter type by a method invocation conversion.
198     * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is
199     *            inaccessible.
200     * @throws InvocationTargetException if the underlying constructor throws an exception.
201     * @throws NoSuchMethodException if the constructor could not be found
202     * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class.
203     * 
204     * @see PrivilegedAccessor#instantiate(Class,Object[])
205     */
206    public static <T> T instantiate(final Class<? extends T> fromClass, final Class<?>[] argumentTypes, final Object[] args)
207       throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException,
208       NoSuchMethodException {
209       return getConstructor(fromClass, argumentTypes).newInstance(args);
210    }
211
212    /**
213     * Calls a method on the given object instance with the given arguments. Arguments can be object types or representations for
214     * primitives.
215     * 
216     * @param instanceOrClass the instance or class to invoke the method on
217     * @param methodSignature the name of the method and the parameters <br>
218     *           (e.g. "myMethod(java.lang.String, com.company.project.MyObject)")
219     * @param arguments an array of objects to pass as arguments
220     * @return the return value of this method or null if void
221     * @throws IllegalAccessException if the method is inaccessible
222     * @throws InvocationTargetException if the underlying method throws an exception.
223     * @throws NoSuchMethodException if no method with the given <code>methodSignature</code> could be found
224     * @throws IllegalArgumentException if an argument couldn't be converted to match the expected type
225     */
226    public static Object invokeMethod(final Object instanceOrClass, final String methodSignature, final Object[] arguments)
227       throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
228       if ((methodSignature.indexOf('(') == -1) || (methodSignature.indexOf('(') >= methodSignature.indexOf(')')))
229       {
230         throw new NoSuchMethodException(methodSignature);
231       }
232       Class<?>[] parameterTypes = getParameterTypes(methodSignature);
233       return getMethod(instanceOrClass, getMethodName(methodSignature), parameterTypes).invoke(instanceOrClass,
234          getCorrectedArguments(parameterTypes, arguments));
235    }
236
237    /**
238     * Gets the given arguments corrected to match the given methodSignature. Correction is necessary for array arguments not to be
239     * mistaken by varargs.
240     * 
241     * @param parameterTypes the method signatue the given arguments should match
242     * @param arguments the arguments that should be corrected
243     * @return the corrected arguments
244     */
245    private static Object[] getCorrectedArguments(Class<?>[] parameterTypes, Object[] arguments) {
246       if (arguments == null)
247       {
248         return arguments;
249       }
250       if (parameterTypes.length > arguments.length)
251       {
252         return arguments;
253       }
254       if (parameterTypes.length < arguments.length)
255       {
256         return getCorrectedArguments(parameterTypes, new Object[] {arguments});
257       }
258
259       Object[] correctedArguments = new Object[arguments.length];
260       int currentArgument = 0;
261       for (Class<?> parameterType : parameterTypes) {
262          correctedArguments[currentArgument] = getCorrectedArgument(parameterType, arguments[currentArgument]);
263          currentArgument++;
264       }
265       return correctedArguments;
266    }
267
268    /**
269     * Gets the given argument corrected to match the given parameterType. Correction is necessary for array arguments not to be
270     * mistaken by varargs.
271     * 
272     * @param parameterType the type to match the given argument upon
273     * @param argument the argument to match the given parameterType
274     * @return the corrected argument
275     */
276    private static Object getCorrectedArgument(Class<?> parameterType, Object argument) {
277       if (!parameterType.isArray() || (argument == null)) {
278          return argument; // normal argument for normal parameterType
279       }
280
281       if (!argument.getClass().isArray()) {
282          return new Object[] {argument};
283       }
284
285       if (parameterType.equals(argument.getClass()))
286        {
287         return argument; // no need to cast
288       }
289
290       // (typed) array argument for (object) array parameterType, elements need to be casted
291       Object correctedArrayArgument = Array.newInstance(parameterType.getComponentType(), Array.getLength(argument));
292       for (int index = 0; index < Array.getLength(argument); index++) {
293          if (parameterType.getComponentType().isPrimitive()) { // rely on autoboxing
294             Array.set(correctedArrayArgument, index, Array.get(argument, index));
295          } else { // cast to expected type
296             try {
297                Array.set(correctedArrayArgument, index, parameterType.getComponentType().cast(Array.get(argument, index)));
298             } catch (ClassCastException e) {
299                throw new IllegalArgumentException("Argument " + argument + " of type " + argument.getClass()
300                   + " does not match expected argument type " + parameterType + ".");
301             }
302          }
303       }
304       return correctedArrayArgument;
305    }
306
307    /**
308     * Sets the value of the named field. If fieldName denotes a static field, provide a class, otherwise provide an instance. If the
309     * fieldName denotes a final field, this method could fail with an IllegalAccessException, since setting the value of final fields
310     * at other times than instantiation can have unpredictable effects.<br/>
311     * <br/>
312     * Example:<br/>
313     * <br/>
314     * <code>
315     * String myString = "Test"; <br/>
316     * <br/>
317     * //setting the private field value<br/>
318     * PrivilegedAccessor.setValue(myString, "value", new char[] {'T', 'e', 's', 't'});<br/>
319     * <br/>
320     * //setting the static final field serialVersionUID - MIGHT FAIL<br/>
321     * PrivilegedAccessor.setValue(myString.getClass(), "serialVersionUID", 1);<br/>
322     * <br/>
323     * </code>
324     * 
325     * @param instanceOrClass the instance or class to set the field
326     * @param fieldName the name of the field
327     * @param value the new value of the field
328     * @throws NoSuchFieldException if no field with the given <code>fieldName</code> can be found
329     * @throws IllegalAccessException possibly if the field was final
330     */
331    public static void setValue(final Object instanceOrClass, final String fieldName, final Object value) throws NoSuchFieldException,
332       IllegalAccessException {
333       Field field = getField(instanceOrClass, fieldName);
334       if (Modifier.isFinal(field.getModifiers())) {
335          PrivilegedAccessor.setValue(field, "modifiers", field.getModifiers() ^ Modifier.FINAL);
336       }
337       field.set(instanceOrClass, value);
338    }
339
340    /**
341     * Gets the class with the given className.
342     * 
343     * @param className the name of the class to get
344     * @return the class for the given className
345     * @throws ClassNotFoundException if the class could not be found
346     */
347    private static Class<?> getClassForName(final String className) throws ClassNotFoundException {
348       if (className.indexOf('[') > -1) {
349          Class<?> clazz = getClassForName(className.substring(0, className.indexOf('[')));
350          return Array.newInstance(clazz, 0).getClass();
351       }
352
353       if (className.indexOf("...") > -1) {
354          Class<?> clazz = getClassForName(className.substring(0, className.indexOf("...")));
355          return Array.newInstance(clazz, 0).getClass();
356       }
357
358       try {
359          return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
360       } catch (ClassNotFoundException e) {
361          return getSpecialClassForName(className);
362       }
363    }
364
365    /**
366     * Maps string representation of primitives to their corresponding classes.
367     */
368    private static final Map<String, Class<?>> PRIMITIVE_MAPPER = new HashMap<String, Class<?>>(8);
369
370    /**
371     * Fills the map with all java primitives and their corresponding classes.
372     */
373    static {
374       PRIMITIVE_MAPPER.put("int", Integer.TYPE);
375       PRIMITIVE_MAPPER.put("float", Float.TYPE);
376       PRIMITIVE_MAPPER.put("double", Double.TYPE);
377       PRIMITIVE_MAPPER.put("short", Short.TYPE);
378       PRIMITIVE_MAPPER.put("long", Long.TYPE);
379       PRIMITIVE_MAPPER.put("byte", Byte.TYPE);
380       PRIMITIVE_MAPPER.put("char", Character.TYPE);
381       PRIMITIVE_MAPPER.put("boolean", Boolean.TYPE);
382    }
383
384    /**
385     * Gets special classes for the given className. Special classes are primitives and "standard" Java types (like String)
386     * 
387     * @param className the name of the class to get
388     * @return the class for the given className
389     * @throws ClassNotFoundException if the class could not be found
390     */
391    private static Class<?> getSpecialClassForName(final String className) throws ClassNotFoundException {
392       if (PRIMITIVE_MAPPER.containsKey(className))
393       {
394         return PRIMITIVE_MAPPER.get(className);
395       }
396
397       if (missesPackageName(className))
398       {
399         return getStandardClassForName(className);
400       }
401
402       throw new ClassNotFoundException(className);
403    }
404
405    /**
406     * Gets a 'standard' java class for the given className.
407     * 
408     * @param className the className
409     * @return the class for the given className (if any)
410     * @throws ClassNotFoundException of no 'standard' java class was found for the given className
411     */
412    private static Class<?> getStandardClassForName(String className) throws ClassNotFoundException {
413       try {
414          return Class.forName("java.lang." + className, false, Thread.currentThread().getContextClassLoader());
415       } catch (ClassNotFoundException e) {
416          try {
417             return Class.forName("java.util." + className, false, Thread.currentThread().getContextClassLoader());
418          } catch (ClassNotFoundException e1) {
419             throw new ClassNotFoundException(className);
420          }
421       }
422    }
423
424    /**
425     * Tests if the given className possibly misses its package name.
426     * 
427     * @param className the className
428     * @return true if the className might miss its package name, otherwise false
429     */
430    private static boolean missesPackageName(String className) {
431       if (className.contains("."))
432       {
433         return false;
434       }
435       if (className.startsWith(className.substring(0, 1).toUpperCase()))
436       {
437         return true;
438       }
439       return false;
440    }
441
442    /**
443     * Gets the constructor for a given class with the given parameters.
444     * 
445     * @param type the class to instantiate
446     * @param parameterTypes the types of the parameters
447     * @return the constructor
448     * @throws NoSuchMethodException if the method could not be found
449     */
450    private static <T> Constructor<T> getConstructor(final Class<T> type, final Class<?>[] parameterTypes) throws NoSuchMethodException {
451       Constructor<T> constructor = type.getDeclaredConstructor(parameterTypes);
452       constructor.setAccessible(true);
453       return constructor;
454    }
455
456    /**
457     * Return the named field from the given instance or class. Returns a static field if instanceOrClass is a class.
458     * 
459     * @param instanceOrClass the instance or class to get the field from
460     * @param fieldName the name of the field to get
461     * @return the field
462     * @throws NoSuchFieldException if no such field can be found
463     * @throws InvalidParameterException if instanceOrClass was null
464     */
465    private static Field getField(final Object instanceOrClass, final String fieldName) throws NoSuchFieldException,
466       InvalidParameterException {
467       if (instanceOrClass == null)
468       {
469         throw new InvalidParameterException("Can't get field on null object/class");
470       }
471
472       Class<?> type = getClass(instanceOrClass);
473
474       try {
475          Field field = type.getDeclaredField(fieldName);
476          field.setAccessible(true);
477          return field;
478       } catch (NoSuchFieldException e) {
479          if (type.getSuperclass() == null)
480         {
481           throw e;
482         }
483          return getField(type.getSuperclass(), fieldName);
484       }
485    }
486
487    /**
488     * 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
489     * 
490     * @param instanceOrClass the instance or class to get the class of
491     * @return the class of the given parameter
492     */
493    private static Class<?> getClass(final Object instanceOrClass) {
494       if (instanceOrClass instanceof Class)
495       {
496         return (Class<?>) instanceOrClass;
497       }
498
499       return instanceOrClass.getClass();
500    }
501
502    /**
503     * Return the named method with a method signature matching classTypes from the given class.
504     * 
505     * @param type the class to get the method from
506     * @param methodName the name of the method to get
507     * @param parameterTypes the parameter-types of the method to get
508     * @return the method
509     * @throws NoSuchMethodException if the method could not be found
510     */
511    private static Method getMethod(final Class<?> type, final String methodName, final Class<?>[] parameterTypes)
512       throws NoSuchMethodException {
513       try {
514          return type.getDeclaredMethod(methodName, parameterTypes);
515       } catch (NoSuchMethodException e) {
516          if (type.getSuperclass() == null)
517         {
518           throw e;
519         }
520          return getMethod(type.getSuperclass(), methodName, parameterTypes);
521       }
522    }
523
524    /**
525     * Gets the method with the given name and parameters from the given instance or class. If instanceOrClass is a class, then we get a
526     * static method.
527     * 
528     * @param instanceOrClass the instance or class to get the method of
529     * @param methodName the name of the method
530     * @param parameterTypes the parameter-types of the method to get
531     * @return the method
532     * @throws NoSuchMethodException if the method could not be found
533     */
534    private static Method getMethod(final Object instanceOrClass, final String methodName, final Class<?>[] parameterTypes)
535       throws NoSuchMethodException {
536       Class<?> type;
537
538       type = getClass(instanceOrClass);
539
540       Method accessMethod = getMethod(type, methodName, parameterTypes);
541       accessMethod.setAccessible(true);
542       return accessMethod;
543    }
544
545    /**
546     * Gets the name of a method.
547     * 
548     * @param methodSignature the signature of the method
549     * @return the name of the method
550     */
551    private static String getMethodName(final String methodSignature) {
552       try {
553          return methodSignature.substring(0, methodSignature.indexOf('(')).trim();
554       } catch (StringIndexOutOfBoundsException e) {
555          assert false : "Signature must have been checked before this method was called";
556          return null;
557       }
558    }
559
560    /**
561     * Gets the types of the parameters.
562     * 
563     * @param parameters the parameters
564     * @return the class-types of the arguments
565     */
566    private static Class<?>[] getParameterTypes(final Object[] parameters) {
567       if (parameters == null)
568       {
569         return new Class[0];
570       }
571
572       Class<?>[] typesOfParameters = new Class[parameters.length];
573
574       for (int i = 0; i < parameters.length; i++) {
575          typesOfParameters[i] = parameters[i].getClass();
576       }
577       return typesOfParameters;
578    }
579
580    /**
581     * Gets the types of the given parameters. If the parameters don't match the given methodSignature an IllegalArgumentException is
582     * thrown.
583     * 
584     * @param methodSignature the signature of the method
585     * @return the parameter types as class[]
586     * @throws NoSuchMethodException if the method could not be found
587     * @throws IllegalArgumentException if one of the given parameters doesn't math the given methodSignature
588     */
589    private static Class<?>[] getParameterTypes(final String methodSignature) throws NoSuchMethodException, IllegalArgumentException {
590       String signature = getSignatureWithoutBraces(methodSignature);
591
592       StringTokenizer tokenizer = new StringTokenizer(signature, ", *");
593       Class<?>[] typesInSignature = new Class[tokenizer.countTokens()];
594
595       for (int x = 0; tokenizer.hasMoreTokens(); x++) {
596          String className = tokenizer.nextToken();
597          try {
598             typesInSignature[x] = getClassForName(className);
599          } catch (ClassNotFoundException e) {
600             NoSuchMethodException noSuchMethodException = new NoSuchMethodException(methodSignature);
601             noSuchMethodException.initCause(e);
602             throw noSuchMethodException;
603          }
604       }
605       return typesInSignature;
606    }
607
608    /**
609     * Gets the parameter types as a string.
610     * 
611     * @param classTypes the types to get as names.
612     * @return the parameter types as a string
613     * 
614     * @see java.lang.Class#argumentTypesToString(Class[])
615     */
616    private static String getParameterTypesAsString(final Class<?>[] classTypes) {
617       assert classTypes != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
618       if (classTypes.length == 0)
619       {
620         return "";
621       }
622
623       StringBuilder parameterTypes = new StringBuilder();
624       for (Class<?> clazz : classTypes) {
625          assert clazz != null : "getParameterTypes() should have been called before this method and should have provided not-null classTypes";
626          parameterTypes.append(clazz.getName()).append(", ");
627       }
628
629       return parameterTypes.substring(0, parameterTypes.length() - 2);
630    }
631
632    /**
633     * Removes the braces around the methods signature.
634     * 
635     * @param methodSignature the signature with braces
636     * @return the signature without braces
637     */
638    private static String getSignatureWithoutBraces(final String methodSignature) {
639       try {
640          return methodSignature.substring(methodSignature.indexOf('(') + 1, methodSignature.indexOf(')'));
641       } catch (IndexOutOfBoundsException e) {
642          assert false : "signature must have been checked before this method";
643          return null;
644       }
645    }
646
647 }