diff --git a/projects/samskivert/src/java/com/samskivert/util/ClassUtil.java b/projects/samskivert/src/java/com/samskivert/util/ClassUtil.java new file mode 100644 index 00000000..885880b9 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/util/ClassUtil.java @@ -0,0 +1,300 @@ +// +// $Id: ClassUtil.java,v 1.1 2001/10/03 02:09:12 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * Class object related utility routines. + * + *
This code was adapted from code provided by Paul Hosler in an
+ * article for Java Report Online
+ * (http://www.javareport.com/html/from_pages/article.asp?id=4276). His
+ * code was adapted from:
+ *
+ *
+ * Kaplan, Lowell. "More programming with generic interfaces." Java
+ * Report, Vol. 5, No. 2, Feb. 2000, pp. 70-74.
+ *
+ */
+public class ClassUtil
+{
+ /**
+ * @param clazz a class.
+ *
+ * @return true if the class is accessible, false otherwise.
+ * Presently returns true if the class is declared public.
+ */
+ public static boolean classIsAccessible (Class clazz)
+ {
+ return Modifier.isPublic(clazz.getModifiers());
+ }
+
+ /**
+ * @param args an object array.
+ *
+ * @return an array of Class objects representing the classes of the
+ * objects in the given Object array. If args is null, a zero-length
+ * Class array is returned. If an element in args is null, then
+ * {@link Void#TYPE} is the corresponding Class in the return array.
+ */
+ public static Class[] getParameterTypesFrom (Object[] args)
+ {
+ Class[] argTypes = null;
+
+ if (args != null) {
+ argTypes = new Class[args.length];
+
+ for (int i = 0; i < args.length; ++i) {
+ argTypes[i] = (args[i] == null) ?
+ Void.TYPE : args[i].getClass();
+ }
+
+ } else {
+ argTypes = new Class[0];
+ }
+
+ return argTypes;
+ }
+
+ /**
+ * 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.
+ */
+ public 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;
+ }
+
+ /**
+ * Searches for the method with the given name and formal parameter
+ * types that is in the nearest accessible class in the class
+ * hierarchy, starting with clazz's superclass. The superclass and
+ * implemented interfaces of clazz are searched, then their
+ * superclasses, etc. until a method is found. Returns null if there
+ * is no such method.
+ *
+ * @param clazz a class.
+ * @param methodName name of a method.
+ * @param paramTypes Class array representing the types of a method's
+ * formal parameters.
+ *
+ * @return the nearest method located, or null if there is no such
+ * method.
+ */
+ public static Method getAccessibleMethodFrom (
+ Class clazz, String methodName, Class[] parameterTypes)
+ {
+ // Look for overridden method in the superclass.
+ Class superclass = clazz.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 clazz represents Object, or an interface, or the
+ // superclass did not have an override. Check implemented
+ // interfaces.
+ Class[] interfaces = clazz.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 clazz a Class.
+ *
+ * @return the class's primitive equivalent, if clazz is a primitive
+ * wrapper. If clazz is primitive, returns clazz. Otherwise,
+ * returns null.
+ */
+ public static Class primitiveEquivalentOf (Class clazz)
+ {
+ return clazz.isPrimitive() ?
+ clazz : (Class)_objectToPrimitiveMap.get(clazz);
+ }
+
+ /**
+ * 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
+ * null, or one of the parameters does not represent a
+ * primitive (e.g. Byte.TYPE), returns false.
+ */
+ public 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);
+ }
+
+ /**
+ * 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);
+ }
+}
diff --git a/projects/samskivert/src/java/com/samskivert/util/MethodFinder.java b/projects/samskivert/src/java/com/samskivert/util/MethodFinder.java
new file mode 100644
index 00000000..b7fa7e4a
--- /dev/null
+++ b/projects/samskivert/src/java/com/samskivert/util/MethodFinder.java
@@ -0,0 +1,365 @@
+//
+// $Id: MethodFinder.java,v 1.1 2001/10/03 02:09:12 mdb Exp $
+//
+// samskivert library - useful routines for java programs
+// Copyright (C) 2001 Michael Bayne
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.samskivert.util;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * Finds methods and constructors that can be invoked reflectively.
+ * Attempts to address some of the limitations of the JDK's {@link
+ * Class#getMethod} and {@link Class#getConstructor}, and other JDK
+ * reflective facilities.
+ *
+ *
Because those methods only match exact method signatures, one is
+ * unable to perform the same method matching that the compiler does at
+ * compile time (e.g. matching the method foo(Exception) when
+ * the user wants to call a method named foo with an
+ * IOException argument) with the basic reflection services.
+ * This class implements the method resolution process according to the
+ * same rules used by a Java compiler. These rules are outlined in the
+ * Java Language Specification, variously in sections 5.1.2, 5.1.4, 5.3,
+ * and 15.12.2.
+ *
+ *
This code was adapted from code provided by Paul Hosler in an + * article for Java Report Online + * (http://www.javareport.com/html/from_pages/article.asp?id=4276). His + * code was adapted from: + * + * + * Kaplan, Lowell. "More programming with generic interfaces." Java + * Report, Vol. 5, No. 2, Feb. 2000, pp. 70-74. + * + */ +public final class MethodFinder +{ + /** + * Constructs a method finder for the supplied class. + * + * @param clazz Class in which I will look for methods and + * constructors. + * + * @exception IllegalArgumentException if clazz is null, or represents + * a primitive, or represents an array type. + */ + public MethodFinder (Class clazz) + { + if (clazz == null) { + throw new IllegalArgumentException("null Class parameter"); + } + + if (clazz.isPrimitive()) { + throw new IllegalArgumentException( + "primitive Class parameter"); + } + + if (clazz.isArray()) { + throw new IllegalArgumentException( + "array Class parameter"); + } + + this.clazz = clazz; + } + + public boolean equals (Object o) + { + if (this == o) + return true; + else if (o == null || getClass() != o.getClass()) + return false; + else { + MethodFinder other = (MethodFinder) o; + return clazz.equals(other.clazz); + } + } + + /** + * Returns the most specific public constructor in my target class + * that accepts the number and type of parameters in the given Class + * array in a reflective invocation. + * + *
A null value or {@link Void#TYPE} in parameterTypes matches a + * corresponding Object or array reference in a constructor's formal + * parameter list, but not a primitive formal parameter. + * + * @param parameterTypes array representing the number and types of + * parameters to look for in the constructor's signature. A null + * array is treated as a zero-length array. + * + * @return Constructor object satisfying the conditions. + * + * @exception NoSuchMethodException if no constructors match the + * criteria, or if the reflective call is ambiguous based on the + * parameter types. + */ + public Constructor findConstructor (Class[] parameterTypes) + throws NoSuchMethodException + { + // make sure the constructor list is loaded + maybeLoadConstructors(); + + if (parameterTypes == null) { + parameterTypes = new Class[0]; + } + + return (Constructor) findMemberIn(ctorList, parameterTypes); + } + + /** + * Returns the most specific public method in my target class that has + * the given name and accepts the number and type of parameters in the + * given Class array in a reflective invocation. + * + *
A null value or {@link Void#TYPE} in parameterTypes will match + * a corresponding Object or array reference in a method's formal + * parameter list, but not a primitive formal parameter. + * + * @param methodName name of the method to search for. + * @param parameterTypes array representing the number and types of + * parameters to look for in the method's signature. A null array is + * treated as a zero-length array. + * + * @return Method object satisfying the conditions. + * + * @exception NoSuchMethodException if no methods match the criteria, + * or if the reflective call is ambiguous based on the parameter + * types, or if methodName is null. + */ + public Method findMethod (String methodName, Class[] parameterTypes) + throws NoSuchMethodException + { + // make sure the constructor list is loaded + maybeLoadMethods(); + + List methodList = (List) methodMap.get(methodName); + if (methodList == null) { + throw new NoSuchMethodException( + "No method named " + clazz.getName() + "." + methodName); + } + + if (parameterTypes == null) { + parameterTypes = new Class[0]; + } + + return (Method) findMemberIn(methodList, parameterTypes); + } + + /** + * Basis of {@link #findConstructor} and {@link #findMethod}. The + * member list fed to this method will be either all {@link + * Constructor} objects or all {@link Method} objects. + */ + private Member findMemberIn (List memberList, Class[] parameterTypes) + throws NoSuchMethodException + { + List matchingMembers = new ArrayList(); + + for (Iterator it = memberList.iterator(); it.hasNext();) { + Member member = (Member) it.next(); + Class[] methodParamTypes = (Class[]) paramMap.get(member); + + // check for exactly equal method signature + if (Arrays.equals(methodParamTypes, parameterTypes)) { + return member; + } + + if (ClassUtil.compatibleClasses( + methodParamTypes, parameterTypes)) { + matchingMembers.add(member); + } + } + + if (matchingMembers.isEmpty()) { + throw new NoSuchMethodException( + "No member in " + clazz.getName() + " matching given args"); + } + if (matchingMembers.size() == 1) { + return (Member) matchingMembers.get(0); + } + + return findMostSpecificMemberIn(matchingMembers); + } + + /** + * @param memberList a list of members (either all constructors or all + * methods). + * + * @return the most specific of all members in the list. + * + * @exception NoSuchMethodException if there is an ambiguity as to + * which is most specific. + */ + private Member findMostSpecificMemberIn (List memberList) + throws NoSuchMethodException + { + List mostSpecificMembers = new ArrayList(); + + for (Iterator memberIt = memberList.iterator(); + memberIt.hasNext();) { + Member member = (Member) memberIt.next(); + + if (mostSpecificMembers.isEmpty()) { + // First guy in is the most specific so far. + mostSpecificMembers.add(member); + + } else { + boolean moreSpecific = true; + boolean lessSpecific = false; + + // Is member more specific than everyone in the + // most-specific set? + for (Iterator specificIt = mostSpecificMembers.iterator(); + specificIt.hasNext();) { + Member moreSpecificMember = (Member) specificIt.next(); + + if (! memberIsMoreSpecific(member, moreSpecificMember)) { + // if the candidate member is not more specific + // than this member, then it's not more specific + // than the entire set, but it may still be + // equivalently specific, so we check that next + moreSpecific = false; + + // we check for a member of equal specificity by + // checking to see if this most specific member is + // explicitly more specific than the candidate + // member. if it is more specific, the candidate + // member can be chucked, otherwise we need to add + // the candidate member to the most-specific set + lessSpecific = + memberIsMoreSpecific(moreSpecificMember, member); + break; + } + } + + if (moreSpecific) { + // Member is the most specific now. + mostSpecificMembers.clear(); + mostSpecificMembers.add(member); + + } else if (! lessSpecific) { + // Add to ambiguity set if mutually unspecific. + mostSpecificMembers.add(member); + } + } + } + + if (mostSpecificMembers.size() > 1) { + throw new NoSuchMethodException( + "Ambiguous request for member in " + + clazz.getName() + + " matching given args" ); + } + + return (Member) mostSpecificMembers.get(0); + } + + public int hashCode () + { + return clazz.hashCode(); + } + + /** + * Loads up the data structures for my target class's constructors. + */ + private void maybeLoadConstructors () + { + if (ctorList == null) { + ctorList = new ArrayList(); + Constructor[] ctors = clazz.getConstructors(); + for (int i = 0; i < ctors.length; ++i) { + ctorList.add(ctors[i]); + paramMap.put(ctors[i], ctors[i].getParameterTypes()); + } + } + } + + /** + * Loads up the data structures for my target class's methods. + */ + private void maybeLoadMethods () + { + if (methodMap == null) { + methodMap = new HashMap(); + Method[] methods = clazz.getMethods(); + + for (int i = 0; i < methods.length; ++i) { + Method m = methods[i]; + String methodName = m.getName(); + Class[] paramTypes = m.getParameterTypes(); + + List list = (List) methodMap.get(methodName); + + if (list == null) { + list = new ArrayList(); + methodMap.put(methodName, list); + } + + if (! ClassUtil.classIsAccessible(clazz)) { + m = ClassUtil.getAccessibleMethodFrom( + clazz, methodName, paramTypes ); + } + + if (m != null) { + list.add(m); + paramMap.put(m, paramTypes); + } + } + } + } + + /** + * @param first a Member. + * @param second a Member. + * + * @return true if the first Member is more specific than the second, + * false otherwise. Specificity is determined according to the + * procedure in the Java Language Specification, section 15.12.2. + */ + private boolean memberIsMoreSpecific (Member first, Member second) + { + Class[] firstParamTypes = (Class[]) paramMap.get(first); + Class[] secondParamTypes = (Class[]) paramMap.get(second); + return ClassUtil.compatibleClasses( + secondParamTypes, firstParamTypes); + } + + /** + * The target class to look for methods and constructors in. + */ + private final Class clazz; + + /** + * Mapping from method name to the Methods in the target class with + * that name. + */ + private final Map methodMap = null; + + /** + * List of the Constructors in the target class. + */ + private final List ctorList = null; + + /** + * Mapping from a Constructor or Method object to the Class objects + * representing its formal parameters. + */ + private final Map paramMap = new HashMap(); +}