/******************************************************************************* * Copyright (c) 2001, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jem.internal.proxy.common; import java.lang.reflect.*; import java.text.MessageFormat; import java.util.*; /** * This is a class to do message/constructor work. * Specifically to find the most appropriate method. */ public class MethodHelper { /* * The class that is used to represent Null class type. * * @since 1.0.0 */ private static class NULL_CLASS { } public static final Class NULL_TYPE = NULL_CLASS.class; static final ArrayList sPrimitivesOrder; static final int sCharPos; static { sPrimitivesOrder = new ArrayList(6); sPrimitivesOrder.add(Byte.TYPE); sPrimitivesOrder.add(Short.TYPE); sPrimitivesOrder.add(Integer.TYPE); sPrimitivesOrder.add(Long.TYPE); sPrimitivesOrder.add(Float.TYPE); sPrimitivesOrder.add(Double.TYPE); // char can be treated like a short for purposes of ordering. sCharPos = sPrimitivesOrder.indexOf(Short.TYPE); } /** * Return whether the type2 can be assigned to type1 in * method argument conversion. */ public static boolean isAssignableFrom(Class type1, Class type2) { if (type1 == type2) return true; // They're the same, so assignable. if (type1.isPrimitive()) { if (type2.isPrimitive()) { if (type1 == Boolean.TYPE || type2 == Boolean.TYPE) return false; // Since not equal and one is boolean and the other isn't, not assignable int type1Pos = (type1 != Character.TYPE) ? sPrimitivesOrder.indexOf(type1) : sCharPos; int type2Pos = (type2 != Character.TYPE) ? sPrimitivesOrder.indexOf(type2) : sCharPos; return type1Pos > type2Pos; // It can be widened if type1 is higher in the order } return false; // primitive to non-primitive, not assignable. } else if (type2 == NULL_TYPE) return true; // NULL_TYPE represents null for us, and null can be assigned to any object else return type1.isAssignableFrom(type2); // Can type2 be assigned to type1 } /** * Every entry in Array2 can be assigned to the corresponding entry in Array1. */ public static boolean isAssignableFrom(Class[] types1, Class[] types2) { if (types1.length != types2.length) return false; // Not the same size, so not compatible. for (int i=0; i<types1.length; i++) { if (!isAssignableFrom(types1[i], types2[i])) return false; } return true; // All are assignable } /** * Return the index of the most compatible method/constructor from the lists passed in. * MethodsList: List of methods (if null then this is for constructors) * ParmsList: List of parms for each method (each entry will be Class[]). */ private static int findMostCompatible(List methods, List parms, String ambiguousName) throws AmbiguousMethodException { // The algorithm used is from the Java Language Specification 15.12.2.2 // Find the maximally specific ones // This is defined as the one that is more specific then all of the rest. // If there are duplicates parms that are maximally specific, then it doesn't matter which choosen // because when invoked the JVM will make sure the right thing is done. // Class[][] parmsCopy = (Class[][]) parms.toArray(new Class[parms.size()][]); int size = parmsCopy.length; // For each entry see if it is maximally specific, i.e. it is more specific then all of the others. nextMethod: for (int i=0; i<size; i++) { // For ctors we don't need to test the declaring class because it will always be the same class. Class dclClassi = methods != null ? ((Method) methods.get(i)).getDeclaringClass() : null; Class[] parmsi = parmsCopy[i]; for (int j=0; j<size; j++) { if (i == j) continue; // Methodi is more specific if // a) Methodi declaring class is assignable to Methodj declaring class // b) Methodi parms are assignable to Methodj parms // // First see if Methodi is more specific, if it is // then throw out Methodj and continue // If Methodi is not compatible to Methodj, go to the next method for i. Methodi is not the most specific // Something else is either more specific or none are ma if (dclClassi != null) { // Step a if (!isAssignableFrom(((Method) methods.get(j)).getDeclaringClass(), dclClassi)) continue nextMethod; // Methodi is not more specific than Methodj, so try next i. } // Step b Class[] parmsj = parmsCopy[j]; if (!isAssignableFrom(parmsj, parmsi)) { // Methodi is not more specific than Methodj, so go to next i. continue nextMethod; } } return i; // Methodi is more specific than all of the other ones. } throw new AmbiguousMethodException(ambiguousName); // There was not one more specific than all of the others. } /** * Find the most compatible method for the given arguments. */ public static Method findCompatibleMethod(Class receiver, String methodName, Class[] arguments) throws NoSuchMethodException, AmbiguousMethodException { try { return receiver.getMethod(methodName, arguments); // Found exact match in public } catch (NoSuchMethodException exc) { if (arguments != null) { // Need to find most compatible one. We will take protected into consideration (i.e. inheritance). ArrayList parmsList = new ArrayList(); // The parm list from each compatible method. ArrayList mthdsList = new ArrayList(); // The list of compatible methods, same order as the parms above. Class cls = receiver; while (cls != null) { Method mthds[] = cls.getDeclaredMethods(); for (int i = 0; i < mthds.length; i++) { Method mthd = mthds[i]; if (!mthd.getName().equals(methodName)) continue; // Not compatible, not same name int modifiers = mthd.getModifiers(); if (!(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))) continue; // Will not call private or package-protected because we don't know the context. Class[] parms = mthd.getParameterTypes(); // If exact match we found a non-public exact match, which is good. if (Arrays.equals(arguments, parms)) return makeMethodAccessable(mthd); if (!isAssignableFrom(parms, arguments)) continue; // Not compatible, parms // It is compatible with the requested method - now see if we already have an exact match from a subclass. Don't want to add it twice. // We are assuming the actual number of compatible methods is small, so this O(n-squared) search is efficient enough. int size = parmsList.size(); for (int j = 0; j < size; j++) { if (Arrays.equals(parms, (Object[]) parmsList.get(j))) continue; } parmsList.add(parms); mthdsList.add(mthd); } cls = cls.getSuperclass(); } // Now have list of compatible methods. if (parmsList.size() == 0) throw throwFixedNoSuchMethod(exc, receiver, methodName, arguments); // None found, so rethrow the exception if (parmsList.size() == 1) return makeMethodAccessable((Method) mthdsList.get(0)); // Only one, so return it // Now find the most compatible method int mostCompatible = findMostCompatible(mthdsList, parmsList, methodName); return makeMethodAccessable((Method) mthdsList.get(mostCompatible)); } else throw throwFixedNoSuchMethod(exc, receiver, methodName, arguments); // None found, so rethrow the exception } } private static Method makeMethodAccessable(Method m) { m.setAccessible(true); // We allow all access, let ide and compiler handle security. return m; } /* * NoSuchMEthodExeception doesn't include the signature. Since these are dynamic searches, the exception itself is useless without * the signature. So we add it. */ private static NoSuchMethodException throwFixedNoSuchMethod(NoSuchMethodException e, Class declareClass, String methodName, Class[] argClasses) { // The default trace doesn't show what method was being searched for, so recreate with that. StringBuffer s = new StringBuffer(); s.append(declareClass.getName()); s.append('.'); s.append(methodName); s.append('('); if (argClasses != null) { for (int i = 0; i < argClasses.length; i++) { if (i > 0) s.append(','); s.append(argClasses[i].getName()); } } s.append(')'); NoSuchMethodException ne = new NoSuchMethodException(s.toString()); ne.setStackTrace(e.getStackTrace()); return ne; } /** * Find the most compatible constructor for the class with the given arguments. * @param receiver class to get the constructor for * @param arguments array of argument types * @return the constructor * @throws NoSuchMethodException no compatible constructor can be found * @throws AmbiguousMethodException there is more than one compatible constructor * @throws IllegalAccessException it can't be accessed. Such as it is a non-static inner class. * * @since 1.2.0 */ public static Constructor findCompatibleConstructor(Class receiver, Class[] arguments) throws NoSuchMethodException, AmbiguousMethodException, IllegalAccessException { if (receiver.getDeclaringClass() != null && !Modifier.isStatic(receiver.getModifiers())) throw new IllegalAccessException(MessageFormat.format(Messages.getString("MethodHelper.NONSTATICINNERCLASS_WARNING"), new Object[] {receiver.getName()})); //$NON-NLS-1$ try { java.lang.reflect.Constructor ctor = receiver.getDeclaredConstructor(arguments); ctor.setAccessible(true); // We allow all access, let ide and compiler handle security. return ctor; // Found exact match } catch (NoSuchMethodException exc) { if (arguments != null) { // Need to find most compatible one. java.lang.reflect.Constructor ctors[] = receiver.getDeclaredConstructors(); ArrayList parmsList = new ArrayList(ctors.length); // The parm list from each compatible method. ArrayList ctorsList = new ArrayList(ctors.length); // The list of compatible methods, same order as the parms above. for (int i = 0; i < ctors.length; i++) { java.lang.reflect.Constructor ctor = ctors[i]; Class[] parms = ctor.getParameterTypes(); if (!isAssignableFrom(parms, arguments)) continue; // Not compatible, parms // It is compatible with the requested method parmsList.add(parms); ctorsList.add(ctor); } // Now have list of compatible methods. if (parmsList.size() == 0) throw exc; // None found, so rethrow the exception if (parmsList.size() == 1) { java.lang.reflect.Constructor ctor = (java.lang.reflect.Constructor) ctorsList.get(0); // Only one, so return it ctor.setAccessible(true); // We allow all access, let ide and compilor handle security. return ctor; } // Now find the most compatible ctor int mostCompatible = findMostCompatible(null, parmsList, receiver.getName()); java.lang.reflect.Constructor ctor = (java.lang.reflect.Constructor) ctorsList.get(mostCompatible); ctor.setAccessible(true); // We allow all access, let ide and compilor handle security. return ctor; } else throw exc; // None found, so rethrow the exception } } }