ClassUtilities.java
- By Paul Holser
- July 24, 2001
Power Java
Limitations of reflective method lookup
by Paul Holser
Listing 6. ClassUtilities.java.
package reflection;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Utility routines for querying Class objects.
*/
final class ClassUtilities {
/**
* Mapping from primitive wrapper Classes to their
* corresponding primitive Classes.
*/
private static final Map objectToPrimitiveMap =
new HashMap(13);
static {
objectToPrimitiveMap.put(Boolean.class, Boolean.TYPE);
objectToPrimitiveMap.put(Byte.class, Byte.TYPE);
objectToPrimitiveMap.put(Character.class, Character.TYPE);
objectToPrimitiveMap.put(Double.class, Double.TYPE);
objectToPrimitiveMap.put(Float.class, Float.TYPE);
objectToPrimitiveMap.put(Integer.class, Integer.TYPE);
objectToPrimitiveMap.put(Long.class, Long.TYPE);
objectToPrimitiveMap.put(Short.class, Short.TYPE);
}
/**
* Mapping from primitive wrapper Classes to the sets of
* primitive classes whose instances can be assigned an
* instance of the first.
*/
private static final Map primitiveWideningsMap =
new HashMap(11);
static {
Set set = new HashSet();
set.add(Short.TYPE);
set.add(Integer.TYPE);
set.add(Long.TYPE);
set.add(Float.TYPE);
set.add(Double.TYPE);
primitiveWideningsMap.put(Byte.TYPE, set);
set = new HashSet();
set.add(Integer.TYPE);
set.add(Long.TYPE);
set.add(Float.TYPE);
set.add(Double.TYPE);
primitiveWideningsMap.put(Short.TYPE, set);
primitiveWideningsMap.put(Character.TYPE, set);
set = new HashSet();
set.add(Long.TYPE);
set.add(Float.TYPE);
set.add(Double.TYPE);
primitiveWideningsMap.put(Integer.TYPE, set);
set = new HashSet();
set.add(Float.TYPE);
set.add(Double.TYPE);
primitiveWideningsMap.put(Long.TYPE, set);
set = new HashSet();
set.add(Double.TYPE);
primitiveWideningsMap.put(Float.TYPE, set);
}
/**
* Do not instantiate. Static methods only.
*/
private ClassUtilities() {}
/**
* @param name FQN of a class, or the name of a primitive type
* @param loader a ClassLoader
* @return the Class for the name given. Primitive types are
* converted to their particular Class object. null, the empty string,
* "null", and "void" yield Void.TYPE. If any classes require
* loading because of this operation, the loading is done by the
* given class loader. Such classes are not initialized, however.
* @exception ClassNotFoundException if name names an
* unknown class or primitive
*/
static Class classForNameOrPrimitive(String name, ClassLoader loader)
throws ClassNotFoundException {
if (name == null || name.equals("") || name.equals("null")
|| name.equals("void"))
return Void.TYPE;
if (name.equals("boolean"))
return Boolean.TYPE;
if (name.equals("byte"))
return Byte.TYPE;
if (name.equals("char"))
return Character.TYPE;
if (name.equals("double"))
return Double.TYPE;
if (name.equals("float"))
return Float.TYPE;
if (name.equals("int"))
return Integer.TYPE;
if (name.equals("long"))
return Long.TYPE;
if (name.equals("short"))
return Short.TYPE;
return Class.forName(name, false, loader);
}
/**
* @param aClass a Class
* @return true if the class is accessible, false otherwise.
* Presently returns true if the class is declared public.
*/
static boolean classIsAccessible(Class aClass) {
return Modifier.isPublic(aClass.getModifiers());
}
/**
* Tells whether instances of the classes in the 'rhs' array
* could be used as parameters to a reflective method
* invocation whose parameter list has types denoted by the
* 'lhs' array.
*
* @param lhs Class array representing the types of the
* formal parameters of a method
* @param rhs Class array representing the types of the
* actual parameters of a method. A null value or
* Void.TYPE is considered to match a corresponding
* Object or array class in lhs, but not a primitive.
* @return true if compatible, false otherwise
*/
static boolean compatibleClasses(Class[] lhs, Class[] rhs) {
if (lhs.length != rhs.length)
return false;
for (int i = 0; i < lhs.length; ++i) {
if (rhs[i] == null || rhs[i].equals(Void.TYPE)) {
if (lhs[i].isPrimitive())
return false;
else
continue;
}
if (! lhs[i].isAssignableFrom(rhs[i])) {
Class lhsPrimEquiv = primitiveEquivalentOf(lhs[i]);
Class rhsPrimEquiv = primitiveEquivalentOf(rhs[i]);
if (! primitiveIsAssignableFrom(lhsPrimEquiv, rhsPrimEquiv))
return false;
}
}
return true;
}
/**
* @param aClass a Class
* @param methodName name of a method
* @param paramTypes Class array representing the types
* of a method's formal parameters
* @return the Method with the given name and formal
* parameter types that is in the nearest accessible class in the
* class hierarchy, starting with aClass's superclass. The
* superclass and implemented interfaces of aClass are
* searched, then their superclasses, etc. until a method is
* found. Returns null if there is no such method.
*/
static Method getAccessibleMethodFrom(Class aClass, String methodName, Class[] parameterTypes) {
// Look for overridden method in the superclass.
Class superclass = aClass.getSuperclass();
Method overriddenMethod = null;
if (superclass != null && classIsAccessible(superclass)) {
try {
overriddenMethod =
superclass.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException _) {
}
if (overriddenMethod != null)
return overriddenMethod;
}
// If here, then aClass represents Object, or an interface, or
// the superclass did not have an override. Check
// implemented interfaces.
Class[] interfaces = aClass.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) {
overriddenMethod = null;
if (classIsAccessible(interfaces[i])) {
try {
overriddenMethod =
interfaces[i].getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException _) {
}
if (overriddenMethod != null)
return overriddenMethod;
}
}
overriddenMethod = null;
// Try superclass's superclass and implemented interfaces.
if (superclass != null) {
overriddenMethod =
getAccessibleMethodFrom(superclass, methodName, parameterTypes);
if (overriddenMethod != null)
return overriddenMethod;
}
// Try implemented interfaces' extended interfaces...
for (int i = 0; i < interfaces.length; ++i) {
overriddenMethod =
getAccessibleMethodFrom(interfaces[i], methodName, parameterTypes);
if (overriddenMethod != null)
return overriddenMethod;
}
// Give up.
return null;
}
/**
* @param aClass a Class
* @return the class's primitive equivalent, if aClass is a
* primitive wrapper. If aClass is primitive, returns aClass.
* Otherwise, returns null.
*/
static Class primitiveEquivalentOf(Class aClass) {
return aClass.isPrimitive()
? aClass
: (Class) objectToPrimitiveMap.get(aClass);
}
/**
* Tells whether an instance of the primitive class
* represented by 'rhs' can be assigned to an instance of the
* primitive class represented by 'lhs'.
*
* @param lhs assignee class
* @param rhs assigned class
* @return true if compatible, false otherwise. If either
* argument is <code>null</code>, or one of the parameters
* does not represent a primitive (e.g. Byte.TYPE), returns
* false.
*/
static boolean primitiveIsAssignableFrom(Class lhs, Class rhs) {
if (lhs == null || rhs == null)
return false;
if (! (lhs.isPrimitive() && rhs.isPrimitive()))
return false;
if (lhs.equals(rhs))
return true;
Set wideningSet = (Set) primitiveWideningsMap.get(rhs);
if (wideningSet == null)
return false;
return wideningSet.contains(lhs);
}
}
About the Author
Paul Holser is a senior consultant at Valtech, an international consulting group specializing in e-business technologies. He can be contacted at [email protected].