You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by nb...@apache.org on 2008/07/25 20:43:40 UTC

svn commit: r679875 - in /velocity/engine/trunk/src/java/org/apache/velocity: runtime/directive/RuntimeMacro.java runtime/resource/Resource.java runtime/resource/ResourceManagerImpl.java util/introspection/ClassMap.java util/introspection/MethodMap.java

Author: nbubna
Date: Fri Jul 25 11:43:39 2008
New Revision: 679875

URL: http://svn.apache.org/viewvc?rev=679875&view=rev
Log:
back out unintended changes in r679862

Modified:
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/RuntimeMacro.java
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/Resource.java
    velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java
    velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/ClassMap.java
    velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java

Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/RuntimeMacro.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/RuntimeMacro.java?rev=679875&r1=679874&r2=679875&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/RuntimeMacro.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/RuntimeMacro.java Fri Jul 25 11:43:39 2008
@@ -19,7 +19,6 @@
  * under the License.
  */
 
-import org.apache.commons.lang.text.StrBuilder;
 import org.apache.velocity.context.InternalContextAdapter;
 import org.apache.velocity.runtime.parser.node.Node;
 import org.apache.velocity.runtime.parser.Token;
@@ -125,35 +124,26 @@
         this.context = context;
         this.node = node;
 
+        StringBuffer buffer = new StringBuffer();
         Token t = node.getFirstToken();
 
-        if (t == node.getLastToken())
+        /**
+         * Retrieve the literal text
+         */
+        while (t != null && t != node.getLastToken())
         {
-            literal = t.image;
+            buffer.append(t.image);
+            t = t.next;
         }
-        else
-        {
-            // guessing that most macros are much longer than
-            // the 32 char default capacity.  let's guess 4x bigger :)
-            StrBuilder text = new StrBuilder(128);
-            /**
-             * Retrieve the literal text
-             */
-            while (t != null && t != node.getLastToken())
-            {
-                text.append(t.image);
-                t = t.next;
-            }
-            if (t != null)
-            {
-                text.append(t.image);
-            }
 
-            /**
-             * Store the literal text
-             */
-            literal = text.toString();
+        if (t != null)
+        {
+            buffer.append(t.image);
         }
+        /**
+         * Store the literal text
+         */
+        literal = buffer.toString();
     }
 
     /**

Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/Resource.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/Resource.java?rev=679875&r1=679874&r2=679875&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/Resource.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/Resource.java Fri Jul 25 11:43:39 2008
@@ -85,11 +85,6 @@
     protected Object data = null;
 
     /**
-     *  Resource type (RESOURCE_TEMPLATE or RESOURCE_CONTENT)
-     */
-    protected int type;
-
-    /**
      *  Default constructor
      */
     public Resource()
@@ -272,20 +267,4 @@
     {
         return data;
     }
-    
-    /**
-     * Sets the type of this Resource (RESOURCE_TEMPLATE or RESOURCE_CONTENT)
-     */
-    public void setType(int type)
-    {
-        this.type = type;
-    }
-    
-    /**
-     * @return type code of the Resource
-     */
-    public int getType()
-    {
-        return type;
-    }
 }

Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java?rev=679875&r1=679874&r2=679875&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java Fri Jul 25 11:43:39 2008
@@ -99,11 +99,11 @@
     public synchronized void initialize(final RuntimeServices rsvc)
         throws Exception
     {
-        if (isInit)
-        {
-            log.error("Re-initialization of ResourceLoader attempted!");
-            return;
-        }
+	if (isInit)
+	{
+	    log.error("Re-initialization of ResourceLoader attempted!");
+	    return;
+	}
 	
         ResourceLoader resourceLoader = null;
 
@@ -259,9 +259,6 @@
      * Gets the named resource. Returned class type corresponds to specified type (i.e. <code>Template</code> to <code>
      * RESOURCE_TEMPLATE</code>).
      *
-     * This method is now unsynchronized which requires that ResourceCache
-     * implementations be thread safe (as the default is).
-     *
      * @param  resourceName  The name of the resource to retrieve.
      * @param  resourceType  The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.).
      * @param  encoding  The character encoding to use.
@@ -272,7 +269,7 @@
      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
      * @throws  Exception  if a problem in parse
      */
-    public Resource getResource(final String resourceName, final int resourceType, final String encoding)
+    public synchronized Resource getResource(final String resourceName, final int resourceType, final String encoding)
         throws ResourceNotFoundException,
             ParseErrorException,
             Exception
@@ -292,29 +289,13 @@
 
         if (resource != null)
         {
+            /*
+             *  refresh the resource
+             */
+
             try
             {
-                // avoids additional method call to refreshResource
-                if (resource.requiresChecking())
-                {
-                    /*
-                     * both loadResource() and refreshResource() now return
-                     * a new Resource instance when they are called
-                     * (put in the cache when appropriate) in order to allow
-                     * several threads to parse the same template simultaneously.
-                     * It is redundant work and will cause more garbage collection but the
-                     * benefit is that it allows concurrent parsing and processing
-                     * without race conditions when multiple requests try to
-                     * refresh/load the same template at the same time.
-                     *
-                     * Another alternative is to limit template parsing/retrieval
-                     * so that only one thread can parse each template at a time
-                     * but that creates a scalability bottleneck.
-                     *
-                     * See VELOCITY-606, VELOCITY-595 and VELOCITY-24
-                     */
-                    resource = refreshResource(resource, encoding);
-                }
+                refreshResource(resource, encoding);
             }
             catch (ResourceNotFoundException rnfe)
             {
@@ -335,7 +316,7 @@
             }
             catch (RuntimeException re)
             {
-        	    throw re;
+        	throw re;
             }
             catch (Exception e)
             {
@@ -349,7 +330,8 @@
             {
                 /*
                  *  it's not in the cache, so load it.
-                 */    
+                 */
+
                 resource = loadResource(resourceName, resourceType, encoding);
 
                 if (resource.getResourceLoader().isCachingOn())
@@ -370,7 +352,7 @@
             }
             catch (RuntimeException re)
             {
-    		    throw re;
+    		throw re;
             }
             catch (Exception e)
             {
@@ -401,7 +383,9 @@
             Exception
     {
         Resource resource = ResourceFactory.getResource(resourceName, resourceType);
+
         resource.setRuntimeServices(rsvc);
+
         resource.setName(resourceName);
         resource.setEncoding(encoding);
 
@@ -491,11 +475,12 @@
      * @throws  ParseErrorException  if template cannot be parsed due to syntax (or other) error.
      * @throws  Exception  if a problem in parse
      */
-    protected Resource refreshResource(Resource resource, final String encoding)
+    protected void refreshResource(final Resource resource, final String encoding)
         throws ResourceNotFoundException,
             ParseErrorException,
             Exception
     {
+
         /*
          * The resource knows whether it needs to be checked
          * or not, and the resource's loader can check to
@@ -504,58 +489,52 @@
          * the input stream and parse it to make a new
          * AST for the resource.
          */
-            
-        /*
-         *  touch() the resource to reset the counters
-         */
-        resource.touch();
-
-        if (resource.isSourceModified())
+        if (resource.requiresChecking())
         {
             /*
-             *  now check encoding info.  It's possible that the newly declared
-             *  encoding is different than the encoding already in the resource
-             *  this strikes me as bad...
+             *  touch() the resource to reset the counters
              */
 
-            if (!org.apache.commons.lang.StringUtils.equals(resource.getEncoding(), encoding))
+            resource.touch();
+
+            if (resource.isSourceModified())
             {
-                log.warn("Declared encoding for template '" +
+                /*
+                 *  now check encoding info.  It's possible that the newly declared
+                 *  encoding is different than the encoding already in the resource
+                 *  this strikes me as bad...
+                 */
+
+                if (!org.apache.commons.lang.StringUtils.equals(resource.getEncoding(), encoding))
+                {
+                    log.warn("Declared encoding for template '" +
                              resource.getName() +
                              "' is different on reload. Old = '" +
                              resource.getEncoding() + "' New = '" + encoding);
 
-                resource.setEncoding(encoding);
-            }
+                    resource.setEncoding(encoding);
+                }
 
-            /*
-             *  read how old the resource is _before_
-             *  processing (=>reading) it
-             */
-            long howOldItWas = resource.getResourceLoader().getLastModified(resource);
+                /*
+                 *  read how old the resource is _before_
+                 *  processing (=>reading) it
+                 */
+                long howOldItWas = resource.getResourceLoader().getLastModified(resource);
 
-            String resourceKey = resource.getType() + resource.getName();
+                /*
+                 *  read in the fresh stream and parse
+                 */
 
-            /* 
-             * we create a copy to avoid partially overwriting a
-             * template which may be in use in another thread
-             */ 
-   
-            Resource newResource = 
-                ResourceFactory.getResource(resource.getName(), resource.getType());
-
-            newResource.setRuntimeServices(rsvc);
-            newResource.setName(resource.getName());
-            newResource.setEncoding(resource.getEncoding());
-            newResource.setResourceLoader(resource.getResourceLoader());
-
-            newResource.process();
-            newResource.setLastModified(howOldItWas);
-            resource = newResource;
+                resource.process();
 
-            globalCache.put(resourceKey, newResource);
+                /*
+                 *  now set the modification info and reset
+                 *  the modification check counters
+                 */
+
+                resource.setLastModified(howOldItWas);
+            }
         }
-        return resource;
     }
 
     /**

Modified: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/ClassMap.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/ClassMap.java?rev=679875&r1=679874&r2=679875&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/ClassMap.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/ClassMap.java Fri Jul 25 11:43:39 2008
@@ -26,7 +26,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import org.apache.commons.lang.text.StrBuilder;
+
 import org.apache.velocity.runtime.log.Log;
 
 /**
@@ -39,7 +39,6 @@
  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
- * @author Nathan Bubna
  * @version $Id$
  */
 public class ClassMap
@@ -73,7 +72,9 @@
             log.debug("== Class: " + clazz);
         }
         
-        methodCache = createMethodCache();
+        methodCache = new MethodCache(log);
+        
+        populateMethodCache();
 
         if (debugReflection && log.isDebugEnabled())
         {
@@ -110,11 +111,10 @@
      * are taken from all the public methods
      * that our class, its parents and their implemented interfaces provide.
      */
-    private MethodCache createMethodCache()
+    private void populateMethodCache()
     {
-        MethodCache methodCache = new MethodCache(log);
 	//
-	// Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start
+	// Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
 	// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
 	// hit java.lang.Object. That is important because it will give us the methods of the declaring class
 	// which might in turn be abstract further up the tree.
@@ -126,60 +126,70 @@
 	// until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
 	// hit the public elements sooner or later because we reflect all the public elements anyway.
 	//
+        List classesToReflect = new ArrayList();
+        
         // Ah, the miracles of Java for(;;) ... 
         for (Class classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
         {
             if (Modifier.isPublic(classToReflect.getModifiers()))
             {
-                populateMethodCacheWith(methodCache, classToReflect);
+                classesToReflect.add(classToReflect);
+                if (debugReflection && log.isDebugEnabled())
+                {
+                    log.debug("Adding " + classToReflect + " for reflection");
+                }
             }
             Class [] interfaces = classToReflect.getInterfaces();
             for (int i = 0; i < interfaces.length; i++)
             {
                 if (Modifier.isPublic(interfaces[i].getModifiers()))
                 {
-                    populateMethodCacheWith(methodCache, interfaces[i]);
+                    classesToReflect.add(interfaces[i]);
+                    if (debugReflection && log.isDebugEnabled())
+                    {
+                        log.debug("Adding " + interfaces[i] + " for reflection");
+                    }
                 }
             }
         }
-        // return the already initialized cache
-        return methodCache;
-    }
-
-    private void populateMethodCacheWith(MethodCache methodCache, Class classToReflect)
-    {
-        if (debugReflection && log.isDebugEnabled())
-        {
-            log.debug("Reflecting " + classToReflect);
-        }
 
-        try
+        for (Iterator it = classesToReflect.iterator(); it.hasNext(); )
         {
-            Method[] methods = classToReflect.getDeclaredMethods();
+            Class classToReflect = (Class) it.next();
+            if (debugReflection && log.isDebugEnabled())
+            {
+                log.debug("Reflecting " + classToReflect);
+            }
+            
 
-            for (int i = 0; i < methods.length; i++)
+            try
             {
-                // Strictly spoken that check shouldn't be necessary
-                // because getMethods only returns public methods.
-                int modifiers = methods[i].getModifiers();
-                if (Modifier.isPublic(modifiers)) //  && !)
+                Method[] methods = classToReflect.getMethods();
+
+                for (int i = 0; i < methods.length; i++)
                 {
-                    // Some of the interfaces contain abstract methods. That is fine, because the actual object must 
-                    // implement them anyway (else it wouldn't be implementing the interface). If we find an abstract
-                    // method in a non-interface, we skip it, because we do want to make sure that no abstract methods end up in
-                    // the cache.                       
-                    if (classToReflect.isInterface() || !Modifier.isAbstract(modifiers))
-                    {
-                        methodCache.put(methods[i]);
+                    // Strictly spoken that check shouldn't be necessary
+                    // because getMethods only returns public methods.
+                    int modifiers = methods[i].getModifiers();
+                    if (Modifier.isPublic(modifiers)) //  && !)
+            	    {
+                        // Some of the interfaces contain abstract methods. That is fine, because the actual object must 
+                        // implement them anyway (else it wouldn't be implementing the interface). If we find an abstract
+                        // method in a non-interface, we skip it, because we do want to make sure that no abstract methods end up in
+                        // the cache.                       
+                        if (classToReflect.isInterface() || !Modifier.isAbstract(modifiers))
+                        {
+                            methodCache.put(methods[i]);
+                        }
                     }
                 }
             }
-        }
-        catch (SecurityException se) // Everybody feels better with...
-        {
-            if (log.isDebugEnabled())
+            catch (SecurityException se) // Everybody feels better with...
             {
-                log.debug("While accessing methods of " + classToReflect + ": ", se);
+                if (log.isDebugEnabled())
+                {
+                    log.debug("While accessing methods of " + classToReflect + ": ", se);
+                }
             }
         }
     }
@@ -192,9 +202,11 @@
      */
     private static final class MethodCache
     {
-        private static final Object CACHE_MISS = new Object();
+        private static final class CacheMiss { }
+        
+        private static final CacheMiss CACHE_MISS = new CacheMiss();
 
-        private static final String NULL_ARG = new Object().getClass().getName();
+        private static final Object OBJECT = new Object();
 
         private static final Map convertPrimitives = new HashMap();
 
@@ -218,7 +230,6 @@
          * name and actual arguments used to find it.
          */
         private final Map cache = new HashMap();
-        private final Map locks = new HashMap();
 
         /** Map of methods that are searchable according to method parameters to find a match */
         private final MethodMap methodMap = new MethodMap();
@@ -244,55 +255,52 @@
          * @return A Method object representing the method to invoke or null.
          * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
          */
-        public Method get(final String name, final Object [] params)
+        public synchronized Method get(final String name, final Object [] params)
                 throws MethodMap.AmbiguousException
         {
-            String methodKey = getLock(makeMethodKey(name, params));
+            String methodKey = makeMethodKey(name, params);
 
-            synchronized (methodKey)
+            Object cacheEntry = cache.get(methodKey);
+
+            // We looked this up before and failed. 
+            if (cacheEntry == CACHE_MISS)
             {
-                Object cacheEntry = cache.get(methodKey);
+                return null;
+            }
 
-                // We looked this up before and failed. 
-                if (cacheEntry == CACHE_MISS)
+            if (cacheEntry == null)
+            {
+                try
                 {
-                    return null;
+                    // That one is expensive...
+                    cacheEntry = methodMap.find(name, params);
                 }
-
-                if (cacheEntry == null)
+                catch(MethodMap.AmbiguousException ae)
                 {
-                    try
-                    {
-                        // That one is expensive...
-                        cacheEntry = methodMap.find(name, params);
-                    }
-                    catch(MethodMap.AmbiguousException ae)
-                    {
-                        /*
-                         *  that's a miss :-)
-                         */
-                        cache.put(methodKey, CACHE_MISS);
-                        throw ae;
-                    }
-
-                    cache.put(methodKey, 
-                            (cacheEntry != null) ? cacheEntry : CACHE_MISS);
+                    /*
+                     *  that's a miss :-)
+                     */
+                    cache.put(methodKey, CACHE_MISS);
+                    throw ae;
                 }
 
-                // Yes, this might just be null.
-
-                return (Method) cacheEntry;
+                cache.put(methodKey, 
+                        (cacheEntry != null) ? cacheEntry : CACHE_MISS);
             }
+
+            // Yes, this might just be null.
+
+            return (Method) cacheEntry;
         }
 
-        private void put(Method method)
+        public synchronized void put(Method method)
         {
             String methodKey = makeMethodKey(method);
-
-            // We don't overwrite methods because we fill the
-            // cache from defined class towards java.lang.Object
-            // and that would cause overridden methods to appear
-            // as if they were not overridden.
+            
+            // We don't overwrite methods. Especially not if we fill the
+            // cache from defined class towards java.lang.Object because 
+            // abstract methods in superclasses would else overwrite concrete
+            // classes further down the hierarchy.
             if (cache.get(methodKey) == null)
             {
                 cache.put(methodKey, method);
@@ -304,18 +312,6 @@
             }
         }
 
-        private final String getLock(String key) {
-            synchronized (locks)
-            {
-                String lock = (String)locks.get(key);
-                if (lock == null)
-                {
-                    return key;
-                }
-                return lock;
-            }
-        }
-
         /**
          * Make a methodKey for the given method using
          * the concatenation of the name and the
@@ -327,15 +323,10 @@
         private String makeMethodKey(final Method method)
         {
             Class[] parameterTypes = method.getParameterTypes();
-            int args = parameterTypes.length;
-            if (args == 0)
-            {
-                return method.getName();
-            }
 
-            StrBuilder methodKey = new StrBuilder((args+1)*16).append(method.getName());
+            StringBuffer methodKey = new StringBuffer(method.getName());
 
-            for (int j = 0; j < args; j++)
+            for (int j = 0; j < parameterTypes.length; j++)
             {
                 /*
                  * If the argument type is primitive then we want
@@ -362,25 +353,18 @@
 
         private String makeMethodKey(String method, Object[] params)
         {
-            int args = params.length;
-            if (args == 0)
-            {
-                return method;
-            }
+            StringBuffer methodKey = new StringBuffer().append(method);
 
-            StrBuilder methodKey = new StrBuilder((args+1)*16).append(method);
-
-            for (int j = 0; j < args; j++)
+            for (int j = 0; j < params.length; j++)
             {
                 Object arg = params[j];
+
                 if (arg == null)
                 {
-                    methodKey.append(NULL_ARG);
-                }
-                else
-                {
-                    methodKey.append(arg.getClass().getName());
+                    arg = OBJECT;
                 }
+
+                methodKey.append(arg.getClass().getName());
             }
 
             return methodKey.toString();

Modified: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java?rev=679875&r1=679874&r2=679875&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java Fri Jul 25 11:43:39 2008
@@ -21,7 +21,7 @@
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -45,7 +45,7 @@
     /**
      * Keep track of all methods with the same name.
      */
-    Map methodByNameMap = new HashMap();
+    Map methodByNameMap = new Hashtable();
 
     /**
      * Add a method to a list of methods by name.
@@ -132,99 +132,99 @@
                     arg == null ? null : arg.getClass();
         }
 
-        return getBestMatch(methodList, classes);
+        return getMostSpecific(methodList, classes);
     }
 
-    private static Method getBestMatch(List methods, Class[] args)
+    /**
+     *  Simple distinguishable exception, used when
+     *  we run across ambiguous overloading.  Caught
+     *  by the introspector.
+     */
+    public static class AmbiguousException extends RuntimeException
+    {
+        /**
+         * Version Id for serializable
+         */
+        private static final long serialVersionUID = -2314636505414551663L;
+    }
+
+
+    private static Method getMostSpecific(List methods, Class[] classes)
+        throws AmbiguousException
     {
-        List equivalentMatches = null;
-        Method bestMatch = null;
-        Class[] bestMatchTypes = null;
-        for (Iterator i = methods.iterator(); i.hasNext(); )
+        LinkedList applicables = getApplicables(methods, classes);
+
+        if(applicables.isEmpty())
         {
-            Method method = (Method)i.next();
-            if (isApplicable(method, args))
+            return null;
+        }
+
+        if(applicables.size() == 1)
+        {
+            return (Method)applicables.getFirst();
+        }
+
+        /*
+         * This list will contain the maximally specific methods. Hopefully at
+         * the end of the below loop, the list will contain exactly one method,
+         * (the most specific method) otherwise we have ambiguity.
+         */
+
+        LinkedList maximals = new LinkedList();
+
+        for (Iterator applicable = applicables.iterator();
+             applicable.hasNext();)
+        {
+            Method app = (Method) applicable.next();
+            Class[] appArgs = app.getParameterTypes();
+            boolean lessSpecific = false;
+
+            for (Iterator maximal = maximals.iterator();
+                 !lessSpecific && maximal.hasNext();)
             {
-                if (bestMatch == null)
-                {
-                    bestMatch = method;
-                    bestMatchTypes = method.getParameterTypes();
-                }
-                else
+                Method max = (Method) maximal.next();
+
+                switch(moreSpecific(appArgs, max.getParameterTypes()))
                 {
-                    Class[] methodTypes = method.getParameterTypes();
-                    switch (compare(methodTypes, bestMatchTypes))
+                    case MORE_SPECIFIC:
+                    {
+                        /*
+                         * This method is more specific than the previously
+                         * known maximally specific, so remove the old maximum.
+                         */
+
+                        maximal.remove();
+                        break;
+                    }
+
+                    case LESS_SPECIFIC:
                     {
-                        case MORE_SPECIFIC:
-                            if (equivalentMatches == null)
-                            {
-                                bestMatch = method;
-                                bestMatchTypes = methodTypes;
-                            }
-                            else
-                            {
-                                // have to beat all other ambiguous ones...
-                                int ambiguities = equivalentMatches.size();
-                                for (int a=0; a < ambiguities; a++)
-                                {
-                                    Method other = (Method)equivalentMatches.get(a);
-                                    switch (compare(methodTypes, other.getParameterTypes()))
-                                    {
-                                        case MORE_SPECIFIC:
-                                            // ...and thus replace them all...
-                                            bestMatch = method;
-                                            bestMatchTypes = methodTypes;
-                                            equivalentMatches = null;
-                                            ambiguities = 0;
-                                            break;
-
-                                        case INCOMPARABLE:
-                                            // ...join them...
-                                            equivalentMatches.add(method);
-                                            break;
-
-                                        case LESS_SPECIFIC:
-                                            // ...or just go away.
-                                            break;
-                                    }
-                                }
-                            }
-                            break;
-
-                        case INCOMPARABLE:
-                            if (equivalentMatches == null)
-                            {
-                                equivalentMatches = new ArrayList(bestMatchTypes.length);
-                            }
-                            equivalentMatches.add(method);
-                            break;
-
-                        case LESS_SPECIFIC:
-                            // do nothing
-                            break;
+                        /*
+                         * This method is less specific than some of the
+                         * currently known maximally specific methods, so we
+                         * won't add it into the set of maximally specific
+                         * methods
+                         */
+
+                        lessSpecific = true;
+                        break;
                     }
                 }
             }
+
+            if(!lessSpecific)
+            {
+                maximals.addLast(app);
+            }
         }
-                
-        if (equivalentMatches != null)
+
+        if(maximals.size() > 1)
         {
+            // We have more than one maximally specific method
             throw new AmbiguousException();
         }
-        return bestMatch;
-    }
 
-    /**
-     *  Simple distinguishable exception, used when
-     *  we run across ambiguous overloading.  Caught
-     *  by the introspector.
-     */
-    public static class AmbiguousException extends RuntimeException
-    {
-        /**
-         * Version Id for serializable
-         */
-        private static final long serialVersionUID = -2314636505414551663L;
+        return (Method)maximals.getFirst();
     }
 
     /**
@@ -235,7 +235,7 @@
      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
      * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
      */
-    private static int compare(Class[] c1, Class[] c2)
+    private static int moreSpecific(Class[] c1, Class[] c2)
     {
         boolean c1MoreSpecific = false;
         boolean c2MoreSpecific = false;
@@ -310,6 +310,31 @@
     }
 
     /**
+     * Returns all methods that are applicable to actual argument types.
+     * @param methods list of all candidate methods
+     * @param classes the actual types of the arguments
+     * @return a list that contains only applicable methods (number of
+     * formal and actual arguments matches, and argument types are assignable
+     * to formal types through a method invocation conversion).
+     */
+    private static LinkedList getApplicables(List methods, Class[] classes)
+    {
+        LinkedList list = new LinkedList();
+
+        for (Iterator imethod = methods.iterator(); imethod.hasNext();)
+        {
+            Method method = (Method) imethod.next();
+
+            if(isApplicable(method, classes))
+            {
+                list.add(method);
+            }
+
+        }
+        return list;
+    }
+
+    /**
      * Returns true if the supplied method is applicable to actual
      * argument types.
      *