KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jicengine > operation > ReflectionUtils


1 package org.jicengine.operation;
2
3 import java.lang.reflect.InvocationTargetException JavaDoc;
4 import java.lang.reflect.Method JavaDoc;
5 import java.lang.reflect.Constructor JavaDoc;
6 import java.lang.reflect.Field JavaDoc;
7 import java.lang.reflect.Modifier JavaDoc;
8 import java.util.Map JavaDoc;
9 import java.util.HashMap JavaDoc;
10
11 /**
12  *
13  * <p>
14  * Copyright (C) 2004 Timo Laitinen
15  * </p>
16  * @author .timo
17  * @version 1.0
18  */

19
20 public class ReflectionUtils {
21
22   private static final String JavaDoc FIELD_CLASS = "class";
23   
24     /**
25      *
26      * @author timo
27      */

28     protected static class NoSuchMethodException extends java.lang.NoSuchMethodException JavaDoc {
29         public NoSuchMethodException(Class JavaDoc actorClass, String JavaDoc methodName)
30         {
31             super("Class '" + actorClass.getName() + "' doesn't have a method '" + methodName + "'.");
32         }
33     }
34
35     /**
36      * Indicates that one or more methods with the right name were found but the
37      * method-parameters didn't match.
38      *
39      * @author timo
40      */

41     protected static class NoMethodWithSuchParametersException extends java.lang.NoSuchMethodException JavaDoc {
42         public NoMethodWithSuchParametersException(Class JavaDoc actorClass, String JavaDoc methodName, Object JavaDoc[] arguments)
43         {
44             super("Class '" + actorClass.getName() + "' doesn't have a method '" + methodName + " accepting arguments (" + getArgumentTypeList(arguments) + ")");
45         }
46     }
47
48     /**
49      *
50      *
51      * @author timo
52      * @created 29. elokuuta 2004
53      */

54     protected static class NoSuchConstructorException extends java.lang.NoSuchMethodException JavaDoc {
55         public NoSuchConstructorException(Class JavaDoc actorClass, Object JavaDoc[] arguments)
56         {
57             super("Class '" + actorClass.getName() + "' doesn't have constructor accepting arguments (" + getArgumentTypeList(arguments) + ")");
58         }
59     }
60
61     private static Map JavaDoc objectTypesToPrimitiveTypes = new HashMap JavaDoc();
62     private static Map JavaDoc primitiveTypesToObjectTypes = new HashMap JavaDoc();
63
64     private static final int ASSIGNABILITY_DIFFERENT_PARAMETER_COUNTS = -1;
65     private static final int ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS = 0;
66     private static final int ASSIGNABILITY_EXACT_MATCH = 1;
67
68     /**
69      * <p>
70      * Mimics method <code>Class.isAssignableFrom</code>, but handles the
71      * conversions between primitives and primitive wrappers automatically.
72      * </p>
73      *
74      * @param class1 Class
75      * @param class2 Class
76      * @return boolean
77      */

78     public static boolean isAssignableFrom(Class JavaDoc class1, Class JavaDoc class2)
79     {
80         if( class1.isAssignableFrom(class2) ){
81             return true;
82         }
83         else {
84             Class JavaDoc primitive = primitiveWrapperToPrimitiveType(class2);
85             if( primitive != null ){
86                 return class1.isAssignableFrom(primitive);
87             }
88             else {
89                 return false;
90             }
91         }
92     }
93
94
95     public static void setFieldValue(Object JavaDoc instance, Class JavaDoc ownerClass, String JavaDoc fieldName, Object JavaDoc fieldValue) throws Exception JavaDoc
96     {
97         try {
98             Field JavaDoc field = ownerClass.getField(fieldName);
99             field.set(instance, fieldValue);
100
101         } catch(NoSuchFieldException JavaDoc e) {
102             // for better error-message
103
throw new NoSuchFieldException JavaDoc("Field '" + fieldName + "' not found in class '" + ownerClass.getName() + "'.");
104         }
105     }
106
107     /**
108      * @param instance the instance whose field is referenced. may be null, if
109      * the field in question is a static field.
110      *
111      * @param ownerClass the class that 'owns' the field.
112      * @param fieldName the name of the field.
113      */

114     public static Object JavaDoc getFieldValue(Object JavaDoc instance, Class JavaDoc ownerClass, String JavaDoc fieldName) throws Exception JavaDoc
115     {
116     if( instance == null && fieldName.equals(FIELD_CLASS) ){
117       // this is not a real static field but an expression
118
// like 'java.lang.String.class'.
119
// reflection does not understand the pseudo-field
120
// 'class', so we handle this situation here
121
// by simply returning the class.
122
return ownerClass;
123     }
124     else {
125         try {
126             Field JavaDoc field = ownerClass.getField(fieldName);
127             return field.get(instance);
128         } catch(NoSuchFieldException JavaDoc e) {
129             // for better error-message
130
throw new NoSuchFieldException JavaDoc("Field '" + fieldName + "' not found in class '" + ownerClass.getName() + "'.");
131         }
132     }
133     }
134
135     protected static Class JavaDoc getActorClass(Object JavaDoc instanceOrClass)
136     {
137         if(instanceOrClass instanceof Class JavaDoc) {
138             return (Class JavaDoc) instanceOrClass;
139         }
140         else {
141             return instanceOrClass.getClass();
142         }
143     }
144
145     /**
146      * <p>
147      * Resolves the 'parameter assignability level' that a set of parameters
148      * has against a set of parameter types.
149      * </p>
150      * <p>
151      * The assignability level is returned as a int value that follows a little
152      * peculiar scheme:
153      * <ul>
154      * <li><b> -1 </b> - no match. even the number of parameters doesn't match. </li>
155      * <li><b> 0 </b> - no match. the number of parameter matches, but at least
156      * one of the parameters has a wrong type. </li>
157      * <li><b> 1 </b> - exact match. the parameter types match the runtime
158      * classes of the parameter objects exactly. </li>
159      * <li><b> 2,3,4,.. </b> - the parameters match. the parameters are assignable
160      * to the expected parameter types, but the expected types are superclasses
161      * or interfaces of the actual types of the parameter objects. the greater
162      * the number, the further away the parameter objects are. </li>
163      * </ul>
164      * <p>
165      * THEREFORE: a positive assignability level that is closer to 1 is better
166      * than a positive value that is further away from it. 1 is the best. values
167      * that are 0 or negative mean that the parameters are incompatible.
168      * </p>
169      *
170      * @param parameterTypes declared parameter types of a method/constructor.
171      * @param parameters runtime parameters given to the method/constructor,
172      * whose types are checked agains the declared types,
173      * in order to find out the correct method/constructor
174      * to call.
175      *
176      * @return The parameterAssignabilityLevel value
177      */

178     private static int getParameterAssignabilityLevel(Class JavaDoc[] parameterTypes, Object JavaDoc[] parameters)
179     {
180         if(parameterTypes.length == parameters.length) {
181             // the number of parameters matches. search further
182

183             // exactly assignable by default
184
int assignability = ASSIGNABILITY_EXACT_MATCH;
185
186             for(int i = 0; i < parameterTypes.length; i++) {
187                 Class JavaDoc parameterType = parameterTypes[i];
188                 Object JavaDoc parameter = parameters[i];
189                 if(parameter == null) {
190                     // we can't resolve the type of a null-parameter!
191
throw new IllegalArgumentException JavaDoc("parameter " + (i + 1) + " was null.");
192                 }
193
194                 Class JavaDoc candidateType = parameter.getClass();
195
196                 if(parameterType.equals(candidateType)) {
197                     // ok, an exact match.
198
assignability *= ASSIGNABILITY_EXACT_MATCH;
199                 }
200                 else if(parameterType.isAssignableFrom(candidateType)) {
201                     // not an exact match but a match anyways
202
// TODO: calculate the distance between these two classes.
203
assignability *= 2;
204                 }
205                 else if(parameterType.isPrimitive()) {
206                     // we have still hope: lets do the primitive conversion.
207
Class JavaDoc correspondingPrimitive = primitiveWrapperToPrimitiveType(candidateType);
208
209                     // if the required parameter type is a primitive, we can't simply
210
// test for equals instead of the assignable thing. int = int,
211
// there are no subclasses!
212
if(correspondingPrimitive != null && correspondingPrimitive.equals(parameterType)) {
213                         // this is considered as an exact match.
214
assignability *= ASSIGNABILITY_EXACT_MATCH;
215                     }
216                     else {
217                         // no match, stop the search.
218
assignability *= ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS;
219                         break;
220                     }
221                 }
222                 else {
223                     // no exact match, no assignable match and no match after primitive
224
// conversions. certainly no match!
225
assignability *= ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS;
226                     break;
227                 }
228             }
229
230             return assignability;
231         }
232         else {
233             // even the number of parameters isn't exact. the worst case
234
return ASSIGNABILITY_DIFFERENT_PARAMETER_COUNTS;
235         }
236     }
237
238
239     /**
240      * Invokes a method.
241      *
242      * @param methodName the name of the method, like
243      * 'addLayer' or 'setName'
244      * @param actor Description of the Parameter
245      * @param arguments Description of the Parameter
246      * @return an Object, if the invoked method
247      * returned something. null if the methods return-type is void.
248      * @throws NoSuchMethodException if no matching method was found
249      * @throws IllegalAccessException see Method.invoke()
250      * @throws IllegalArgumentException see Method.invoke()
251      * @throws InvocationTargetException if the invoked method throwed an
252      * Exception
253      */

254     public static Object JavaDoc invokeMethod(Object JavaDoc actor, String JavaDoc methodName, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc, InvocationTargetException JavaDoc
255     {
256         if(actor == null) {
257             throw new NullPointerException JavaDoc("when calling method '" + methodName + "' (with " + arguments.length + " arguments)");
258         }
259         else {
260             return invokeMethod(actor, actor.getClass(), methodName, arguments);
261         }
262     }
263
264     public static Object JavaDoc invokeStaticMethod(Class JavaDoc actorClass, String JavaDoc methodName, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc, InvocationTargetException JavaDoc
265     {
266         if(actorClass == null) {
267             throw new NullPointerException JavaDoc("when calling static method '" + methodName + "' (with " + arguments.length + " arguments)");
268         }
269         else {
270             return invokeMethod(null, actorClass, methodName, arguments);
271         }
272     }
273
274     public static Object JavaDoc instantiate(Class JavaDoc instantiatedClass, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, InstantiationException JavaDoc, IllegalAccessException JavaDoc, InvocationTargetException JavaDoc
275     {
276         // find the constructor.
277
Constructor JavaDoc constructor = findConstructor(instantiatedClass, arguments);
278
279         // instantiate object
280
try {
281             return constructor.newInstance(arguments);
282         } catch(IllegalArgumentException JavaDoc e) {
283             throw new IllegalArgumentException JavaDoc("Problems instantiating with constructor '" + constructor + "'");
284         }
285     }
286
287     private static Constructor JavaDoc findConstructor(Class JavaDoc instantiatedClass, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, InstantiationException JavaDoc, IllegalAccessException JavaDoc
288     {
289         Constructor JavaDoc[] constructors = instantiatedClass.getConstructors();
290         Constructor JavaDoc match = null;
291         int parameterAssignability = ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS;
292
293         for(int i = 0; i < constructors.length; i++) {
294             Constructor JavaDoc candidate = constructors[i];
295             Class JavaDoc[] parameterTypes = candidate.getParameterTypes();
296
297             int candidateParameterAssignability = getParameterAssignabilityLevel(parameterTypes, arguments);
298
299             if(candidateParameterAssignability > ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS) {
300                 // parameters a assignable. find out how assignable
301

302                 if(candidateParameterAssignability == ASSIGNABILITY_EXACT_MATCH) {
303                     // the parameters match exactly. we can stop the seach here!
304
match = candidate;
305                     parameterAssignability = candidateParameterAssignability;
306                     break;
307                 }
308                 else if(parameterAssignability < ASSIGNABILITY_EXACT_MATCH || candidateParameterAssignability < parameterAssignability) {
309                     // not an exact match but a better match than our previous match
310
// (if had one).
311
// this is the best candidate so far, but we have to continue the
312
// search.
313
match = candidate;
314                     parameterAssignability = candidateParameterAssignability;
315                     continue;
316                 }
317             }
318         }
319
320         if(match == null) {
321             throw new NoSuchConstructorException(instantiatedClass, arguments);
322         }
323         else {
324             return match;
325         }
326     }
327
328     /**
329      * @param objectClass Description of the Parameter
330      * @return the Class representing a primitive: if the argument
331      * Class is java.lang.Integer, returns Integer.TYPe, etc.
332      * null if the argument class is not a wrapper.
333      */

334     protected static Class JavaDoc primitiveWrapperToPrimitiveType(Class JavaDoc objectClass)
335     {
336         return (Class JavaDoc) objectTypesToPrimitiveTypes.get(objectClass);
337     }
338
339     /**
340      * @param primitiveType Description of the Parameter
341      * @return null if the argument class is not a primitive type.
342      */

343     protected static Class JavaDoc primitiveTypeToWrapperType(Class JavaDoc primitiveType)
344     {
345         return (Class JavaDoc) primitiveTypesToObjectTypes.get(primitiveType);
346     }
347
348     /**
349      *
350      * @param arguments Object[]
351      * @return String types in format [arg1 class], [arg2 class], etc.
352      */

353     protected static String JavaDoc getArgumentTypeList(Object JavaDoc[] arguments)
354     {
355         String JavaDoc paramString = "";
356         for(int i = 0; i < arguments.length; i++) {
357             if(arguments[i] == null) {
358                 paramString += "" + arguments[i];
359             }
360             else {
361                 // should array and primitive types be handled separately?
362
paramString += arguments[i].getClass().getName();
363             }
364
365             if((i + 1) < arguments.length) {
366                 paramString += ",";
367             }
368         }
369         return paramString;
370     }
371
372     /**
373      * a method for invoking both instance methods and static methods dynamically.
374      *
375      * @param actor the instance whose method is
376      * invoked. null, if a static method is in question. NOTE: the null means
377      * that the method must be static. filter unintentional null-values away
378      * before calling this method!
379      * @param actorClass the class that owns the invoked
380      * method. this should be the class of the actor.
381      * @param methodName name of the method
382      * @param parameters parameters as Object[]
383      * @return Description of the Return Value
384      * @throws java.lang.NoSuchMethodException Description of the Exception
385      * @throws IllegalAccessException Description of the Exception
386      * @throws IllegalArgumentException Description of the Exception
387      * @throws InvocationTargetException Description of the Exception
388      */

389     private static Object JavaDoc invokeMethod(Object JavaDoc actor, Class JavaDoc actorClass, String JavaDoc methodName, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc, InvocationTargetException JavaDoc
390     {
391         // some validity checks
392
if(methodName == null) {
393             throw new IllegalArgumentException JavaDoc("Can't invoke a method: method name was null");
394         }
395         if(arguments == null) {
396             throw new IllegalArgumentException JavaDoc("Can't invoke a method: arguments[] was null");
397         }
398
399         if(!Modifier.isPublic(actorClass.getModifiers())) {
400             // problem: a non-public class. we have no permissions to read it.
401
// this support has to be implemented later.
402
throw new UnsupportedOperationException JavaDoc("Class '" + actorClass.getName() + "' is private or protected. only public classes are supported currently.");
403         }
404
405         Method JavaDoc method;
406
407         // we try first the method Class.getMethod().
408
// it works only if the types of the parameters match exactly the
409
// declared parameter types of a method in the owner class.
410
// but it is fast - its worth trying although it might result in
411
// exception.
412
//
413
// note: we could use some heuristics for deciding whether it is better
414
// to try getMethod() or manual search first.
415
try {
416             method = actorClass.getMethod(methodName, getTypes(arguments));
417         } catch (java.lang.NoSuchMethodException JavaDoc e){
418             method = findMethod(actorClass, methodName, arguments);
419         }
420
421         // call the method and return the result.
422
// note: a better error message for situations where the method was supposed
423
// to be static but wasn't? now, only a nullpointer is thrown..
424
return method.invoke(actor, arguments);
425     }
426
427     private static Method JavaDoc findMethod(Class JavaDoc actorClass, String JavaDoc methodName, Object JavaDoc[] arguments) throws java.lang.NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, IllegalArgumentException JavaDoc, InvocationTargetException JavaDoc
428     {
429         Method JavaDoc[] methods = actorClass.getMethods();
430         Method JavaDoc match = null;
431         int parameterAssignability = ASSIGNABILITY_DIFFERENT_PARAMETER_COUNTS;
432         boolean foundMethodWithTheSameName = false;
433
434         for(int i = 0; i < methods.length; i++) {
435             Method JavaDoc candidate = methods[i];
436             if(candidate.getName().equals(methodName)) {
437                 // good, the name matches..
438
foundMethodWithTheSameName = true;
439                 Class JavaDoc[] parameterTypes = candidate.getParameterTypes();
440
441                 int candidateParameterAssignability = getParameterAssignabilityLevel(parameterTypes, arguments);
442
443                 if(candidateParameterAssignability > ASSIGNABILITY_NON_ASSIGNABLE_PARAMETERS) {
444                     // parameters a assignable. find out how assignable
445

446                     if(candidateParameterAssignability == ASSIGNABILITY_EXACT_MATCH) {
447                         // the parameters match exactly. we can stop the seach here!
448
match = candidate;
449                         parameterAssignability = candidateParameterAssignability;
450                         break;
451                     }
452                     else if(parameterAssignability < ASSIGNABILITY_EXACT_MATCH || candidateParameterAssignability < parameterAssignability) {
453                         // not an exact match but a better match than our previous match
454
// (if had one).
455
// this is the best candidate so far, but we have to continue the
456
// search.
457

458                         // NOTE: 16.5.2005: is the test correct? shouldn't it use '>'
459
// instead of '<'..
460

461                         match = candidate;
462                         parameterAssignability = candidateParameterAssignability;
463                         continue;
464                     }
465                 }
466             }
467         }
468
469         if(match == null) {
470             // no method found.
471
if(foundMethodWithTheSameName) {
472                 throw new NoMethodWithSuchParametersException(actorClass, methodName, arguments);
473             }
474             else {
475                 throw new ReflectionUtils.NoSuchMethodException(actorClass, methodName);
476             }
477         }
478         else {
479             return match;
480         }
481     }
482
483     protected static Class JavaDoc[] getTypes(Object JavaDoc[] parameters)
484     {
485         Class JavaDoc[] types = new Class JavaDoc[parameters.length];
486         for (int i = 0; i < parameters.length; i++) {
487             types[i] = parameters[i].getClass();
488         }
489         return types;
490     }
491
492     static {
493         objectTypesToPrimitiveTypes.put(Double JavaDoc.class, Double.TYPE);
494         objectTypesToPrimitiveTypes.put(Integer JavaDoc.class, Integer.TYPE);
495         objectTypesToPrimitiveTypes.put(Long JavaDoc.class, Long.TYPE);
496         objectTypesToPrimitiveTypes.put(Character JavaDoc.class, Character.TYPE);
497         objectTypesToPrimitiveTypes.put(Boolean JavaDoc.class, Boolean.TYPE);
498         objectTypesToPrimitiveTypes.put(Byte JavaDoc.class, Byte.TYPE);
499     objectTypesToPrimitiveTypes.put(Float JavaDoc.class, Float.TYPE);
500     objectTypesToPrimitiveTypes.put(Short JavaDoc.class, Short.TYPE);
501
502         primitiveTypesToObjectTypes.put(Double.TYPE, Double JavaDoc.class);
503         primitiveTypesToObjectTypes.put(Integer.TYPE, Integer JavaDoc.class);
504         primitiveTypesToObjectTypes.put(Long.TYPE, Long JavaDoc.class);
505         primitiveTypesToObjectTypes.put(Character.TYPE, Character JavaDoc.class);
506         primitiveTypesToObjectTypes.put(Boolean.TYPE, Boolean JavaDoc.class);
507         primitiveTypesToObjectTypes.put(Byte.TYPE, Byte JavaDoc.class);
508     primitiveTypesToObjectTypes.put(Float.TYPE, Float JavaDoc.class);
509     primitiveTypesToObjectTypes.put(Short.TYPE, Short JavaDoc.class);
510
511     }
512 }
513
Popular Tags