You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by st...@apache.org on 2012/08/30 01:24:31 UTC

svn commit: r1378767 - in /maven/shared/trunk/maven-shared-utils/src: main/java/org/apache/maven/shared/utils/introspection/ main/resources/META-INF/ test/java/org/apache/maven/shared/utils/introspection/

Author: struberg
Date: Wed Aug 29 23:24:31 2012
New Revision: 1378767

URL: http://svn.apache.org/viewvc?rev=1378767&view=rev
Log:
MSHARED-236 add ALv2 (c) ASF ReflectionValueExtractor

Added:
    maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/
    maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java   (with props)
    maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java   (with props)
    maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java   (with props)
    maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/
    maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE   (with props)
    maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/
    maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java   (with props)

Added: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java
URL: http://svn.apache.org/viewvc/maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java?rev=1378767&view=auto
==============================================================================
--- maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java (added)
+++ maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java Wed Aug 29 23:24:31 2012
@@ -0,0 +1,519 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
+ * method name and the names of classes that make up the parameters.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ClassMap
+{
+    private static final class CacheMiss
+    {
+    }
+
+    private static final CacheMiss CACHE_MISS = new CacheMiss();
+    private static final Object OBJECT = new Object();
+
+    /**
+     * Class passed into the constructor used to as
+     * the basis for the Method map.
+     */
+
+    private final Class clazz;
+
+    /**
+     * Cache of Methods, or CACHE_MISS, keyed by method
+     * name and actual arguments used to find it.
+     */
+    private Map methodCache = new Hashtable();
+
+    private MethodMap methodMap = new MethodMap();
+
+    /**
+     * Standard constructor
+     */
+    public ClassMap( Class clazz )
+    {
+        this.clazz = clazz;
+        populateMethodCache();
+    }
+
+    /**
+     * @return the class object whose methods are cached by this map.
+     */
+    Class getCachedClass()
+    {
+        return clazz;
+    }
+
+    /**
+     * Find a Method using the methodKey
+     * provided.
+     * <p/>
+     * Look in the methodMap for an entry.  If found,
+     * it'll either be a CACHE_MISS, in which case we
+     * simply give up, or it'll be a Method, in which
+     * case, we return it.
+     * <p/>
+     * If nothing is found, then we must actually go
+     * and introspect the method from the MethodMap.
+     */
+    public Method findMethod( String name, Object[] params )
+        throws MethodMap.AmbiguousException
+    {
+        String methodKey = makeMethodKey( name, params );
+        Object cacheEntry = methodCache.get( methodKey );
+
+        if ( cacheEntry == CACHE_MISS )
+        {
+            return null;
+        }
+
+        if ( cacheEntry == null )
+        {
+            try
+            {
+                cacheEntry = methodMap.find( name,
+                                             params );
+            }
+            catch ( MethodMap.AmbiguousException ae )
+            {
+                /*
+                 *  that's a miss :)
+                 */
+
+                methodCache.put( methodKey,
+                                 CACHE_MISS );
+
+                throw ae;
+            }
+
+            if ( cacheEntry == null )
+            {
+                methodCache.put( methodKey,
+                                 CACHE_MISS );
+            }
+            else
+            {
+                methodCache.put( methodKey,
+                                 cacheEntry );
+            }
+        }
+
+        // Yes, this might just be null.
+
+        return (Method) cacheEntry;
+    }
+
+    /**
+     * Populate the Map of direct hits. These
+     * are taken from all the public methods
+     * that our class provides.
+     */
+    private void populateMethodCache()
+    {
+        StringBuffer methodKey;
+
+        /*
+         *  get all publicly accessible methods
+         */
+
+        Method[] methods = getAccessibleMethods( clazz );
+
+        /*
+         * map and cache them
+         */
+
+        for ( int i = 0; i < methods.length; i++ )
+        {
+            Method method = methods[i];
+
+            /*
+             *  now get the 'public method', the method declared by a
+             *  public interface or class. (because the actual implementing
+             *  class may be a facade...
+             */
+
+            Method publicMethod = getPublicMethod( method );
+
+            /*
+             *  it is entirely possible that there is no public method for
+             *  the methods of this class (i.e. in the facade, a method
+             *  that isn't on any of the interfaces or superclass
+             *  in which case, ignore it.  Otherwise, map and cache
+             */
+
+            if ( publicMethod != null )
+            {
+                methodMap.add( publicMethod );
+                methodCache.put( makeMethodKey( publicMethod ), publicMethod );
+            }
+        }
+    }
+
+    /**
+     * Make a methodKey for the given method using
+     * the concatenation of the name and the
+     * types of the method parameters.
+     */
+    private String makeMethodKey( Method method )
+    {
+        Class[] parameterTypes = method.getParameterTypes();
+
+        StringBuffer methodKey = new StringBuffer( method.getName() );
+
+        for ( int j = 0; j < parameterTypes.length; j++ )
+        {
+            /*
+             * If the argument type is primitive then we want
+             * to convert our primitive type signature to the
+             * corresponding Object type so introspection for
+             * methods with primitive types will work correctly.
+             */
+            if ( parameterTypes[j].isPrimitive() )
+            {
+                if ( parameterTypes[j].equals( Boolean.TYPE ) )
+                    methodKey.append( "java.lang.Boolean" );
+                else if ( parameterTypes[j].equals( Byte.TYPE ) )
+                    methodKey.append( "java.lang.Byte" );
+                else if ( parameterTypes[j].equals( Character.TYPE ) )
+                    methodKey.append( "java.lang.Character" );
+                else if ( parameterTypes[j].equals( Double.TYPE ) )
+                    methodKey.append( "java.lang.Double" );
+                else if ( parameterTypes[j].equals( Float.TYPE ) )
+                    methodKey.append( "java.lang.Float" );
+                else if ( parameterTypes[j].equals( Integer.TYPE ) )
+                    methodKey.append( "java.lang.Integer" );
+                else if ( parameterTypes[j].equals( Long.TYPE ) )
+                    methodKey.append( "java.lang.Long" );
+                else if ( parameterTypes[j].equals( Short.TYPE ) )
+                    methodKey.append( "java.lang.Short" );
+            }
+            else
+            {
+                methodKey.append( parameterTypes[j].getName() );
+            }
+        }
+
+        return methodKey.toString();
+    }
+
+    private static String makeMethodKey( String method, Object[] params )
+    {
+        StringBuffer methodKey = new StringBuffer().append( method );
+
+        for ( int j = 0; j < params.length; j++ )
+        {
+            Object arg = params[j];
+
+            if ( arg == null )
+            {
+                arg = OBJECT;
+            }
+
+            methodKey.append( arg.getClass().getName() );
+        }
+
+        return methodKey.toString();
+    }
+
+    /**
+     * Retrieves public methods for a class. In case the class is not
+     * public, retrieves methods with same signature as its public methods
+     * from public superclasses and interfaces (if they exist). Basically
+     * upcasts every method to the nearest acccessible method.
+     */
+    private static Method[] getAccessibleMethods( Class clazz )
+    {
+        Method[] methods = clazz.getMethods();
+
+        /*
+         *  Short circuit for the (hopefully) majority of cases where the
+         *  clazz is public
+         */
+
+        if ( Modifier.isPublic( clazz.getModifiers() ) )
+        {
+            return methods;
+        }
+
+        /*
+         *  No luck - the class is not public, so we're going the longer way.
+         */
+
+        MethodInfo[] methodInfos = new MethodInfo[methods.length];
+
+        for ( int i = methods.length; i-- > 0; )
+        {
+            methodInfos[i] = new MethodInfo( methods[i] );
+        }
+
+        int upcastCount = getAccessibleMethods( clazz, methodInfos, 0 );
+
+        /*
+         *  Reallocate array in case some method had no accessible counterpart.
+         */
+
+        if ( upcastCount < methods.length )
+        {
+            methods = new Method[upcastCount];
+        }
+
+        int j = 0;
+        for ( int i = 0; i < methodInfos.length; ++i )
+        {
+            MethodInfo methodInfo = methodInfos[i];
+            if ( methodInfo.upcast )
+            {
+                methods[j++] = methodInfo.method;
+            }
+        }
+        return methods;
+    }
+
+    /**
+     * Recursively finds a match for each method, starting with the class, and then
+     * searching the superclass and interfaces.
+     *
+     * @param clazz       Class to check
+     * @param methodInfos array of methods we are searching to match
+     * @param upcastCount current number of methods we have matched
+     * @return count of matched methods
+     */
+    private static int getAccessibleMethods( Class clazz, MethodInfo[] methodInfos, int upcastCount )
+    {
+        int l = methodInfos.length;
+
+        /*
+         *  if this class is public, then check each of the currently
+         *  'non-upcasted' methods to see if we have a match
+         */
+
+        if ( Modifier.isPublic( clazz.getModifiers() ) )
+        {
+            for ( int i = 0; i < l && upcastCount < l; ++i )
+            {
+                try
+                {
+                    MethodInfo methodInfo = methodInfos[i];
+
+                    if ( !methodInfo.upcast )
+                    {
+                        methodInfo.tryUpcasting( clazz );
+                        upcastCount++;
+                    }
+                }
+                catch ( NoSuchMethodException e )
+                {
+                    /*
+                     *  Intentionally ignored - it means
+                     *  it wasn't found in the current class
+                     */
+                }
+            }
+
+            /*
+             *  Short circuit if all methods were upcast
+             */
+
+            if ( upcastCount == l )
+            {
+                return upcastCount;
+            }
+        }
+
+        /*
+         *   Examine superclass
+         */
+
+        Class superclazz = clazz.getSuperclass();
+
+        if ( superclazz != null )
+        {
+            upcastCount = getAccessibleMethods( superclazz, methodInfos, upcastCount );
+
+            /*
+             *  Short circuit if all methods were upcast
+             */
+
+            if ( upcastCount == l )
+            {
+                return upcastCount;
+            }
+        }
+
+        /*
+         *  Examine interfaces. Note we do it even if superclazz == null.
+         *  This is redundant as currently java.lang.Object does not implement
+         *  any interfaces, however nothing guarantees it will not in future.
+         */
+
+        Class[] interfaces = clazz.getInterfaces();
+
+        for ( int i = interfaces.length; i-- > 0; )
+        {
+            upcastCount = getAccessibleMethods( interfaces[i], methodInfos, upcastCount );
+
+            /*
+             *  Short circuit if all methods were upcast
+             */
+
+            if ( upcastCount == l )
+            {
+                return upcastCount;
+            }
+        }
+
+        return upcastCount;
+    }
+
+    /**
+     * For a given method, retrieves its publicly accessible counterpart.
+     * This method will look for a method with same name
+     * and signature declared in a public superclass or implemented interface of this
+     * method's declaring class. This counterpart method is publicly callable.
+     *
+     * @param method a method whose publicly callable counterpart is requested.
+     * @return the publicly callable counterpart method. Note that if the parameter
+     *         method is itself declared by a public class, this method is an identity
+     *         function.
+     */
+    public static Method getPublicMethod( Method method )
+    {
+        Class clazz = method.getDeclaringClass();
+
+        /*
+         *   Short circuit for (hopefully the majority of) cases where the declaring
+         *   class is public.
+         */
+
+        if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
+        {
+            return method;
+        }
+
+        return getPublicMethod( clazz, method.getName(), method.getParameterTypes() );
+    }
+
+    /**
+     * Looks up the method with specified name and signature in the first public
+     * superclass or implemented interface of the class.
+     *
+     * @param clazz      the class whose method is sought
+     * @param name       the name of the method
+     * @param paramTypes the classes of method parameters
+     */
+    private static Method getPublicMethod( Class clazz, String name, Class[] paramTypes )
+    {
+        /*
+         *  if this class is public, then try to get it
+         */
+
+        if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
+        {
+            try
+            {
+                return clazz.getMethod( name, paramTypes );
+            }
+            catch ( NoSuchMethodException e )
+            {
+                /*
+                 *  If the class does not have the method, then neither its
+                 *  superclass nor any of its interfaces has it so quickly return
+                 *  null.
+                 */
+                return null;
+            }
+        }
+
+        /*
+         *  try the superclass
+         */
+
+
+        Class superclazz = clazz.getSuperclass();
+
+        if ( superclazz != null )
+        {
+            Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
+
+            if ( superclazzMethod != null )
+            {
+                return superclazzMethod;
+            }
+        }
+
+        /*
+         *  and interfaces
+         */
+
+        Class[] interfaces = clazz.getInterfaces();
+
+        for ( int i = 0; i < interfaces.length; ++i )
+        {
+            Method interfaceMethod = getPublicMethod( interfaces[i], name, paramTypes );
+
+            if ( interfaceMethod != null )
+            {
+                return interfaceMethod;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Used for the iterative discovery process for public methods.
+     */
+    private static final class MethodInfo
+    {
+        Method method;
+        String name;
+        Class[] parameterTypes;
+        boolean upcast;
+
+        MethodInfo( Method method )
+        {
+            this.method = null;
+            name = method.getName();
+            parameterTypes = method.getParameterTypes();
+            upcast = false;
+        }
+
+        void tryUpcasting( Class clazz )
+            throws NoSuchMethodException
+        {
+            method = clazz.getMethod( name, parameterTypes );
+            name = null;
+            parameterTypes = null;
+            upcast = true;
+        }
+    }
+}

Propchange: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java
URL: http://svn.apache.org/viewvc/maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java?rev=1378767&view=auto
==============================================================================
--- maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java (added)
+++ maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java Wed Aug 29 23:24:31 2012
@@ -0,0 +1,463 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @version $Id$
+ */
+public class MethodMap
+{
+    private static final int MORE_SPECIFIC = 0;
+    private static final int LESS_SPECIFIC = 1;
+    private static final int INCOMPARABLE = 2;
+
+    /**
+     * Keep track of all methods with the same name.
+     */
+    Map<String, List<Method>> methodByNameMap = new Hashtable<String, List<Method>>();
+
+    /**
+     * Add a method to a list of methods by name.
+     * For a particular class we are keeping track
+     * of all the methods with the same name.
+     * @param method The method
+     */
+    public void add(Method method)
+    {
+        String methodName = method.getName();
+
+        List<Method> l = get( methodName );
+
+        if ( l == null)
+        {
+            l = new ArrayList<Method>();
+            methodByNameMap.put(methodName, l);
+        }
+
+        l.add(method);
+    }
+
+    /**
+     * Return a list of methods with the same name.
+     *
+     * @param key The name of the method.
+     * @return List list of methods
+     */
+    public List<Method> get(String key)
+    {
+        return methodByNameMap.get(key);
+    }
+
+    /**
+     *  <p>
+     *  Find a method.  Attempts to find the
+     *  most specific applicable method using the
+     *  algorithm described in the JLS section
+     *  15.12.2 (with the exception that it can't
+     *  distinguish a primitive type argument from
+     *  an object type argument, since in reflection
+     *  primitive type arguments are represented by
+     *  their object counterparts, so for an argument of
+     *  type (say) java.lang.Integer, it will not be able
+     *  to decide between a method that takes int and a
+     *  method that takes java.lang.Integer as a parameter.
+     *  </p>
+     *
+     *  <p>
+     *  This turns out to be a relatively rare case
+     *  where this is needed - however, functionality
+     *  like this is needed.
+     *  </p>
+     *
+     *  @param methodName name of method
+     *  @param args the actual arguments with which the method is called
+     *  @return the most specific applicable method, or null if no
+     *  method is applicable.
+     *  @throws AmbiguousException if there is more than one maximally
+     *  specific applicable method
+     */
+    public Method find(String methodName, Object[] args)
+        throws AmbiguousException
+    {
+        List methodList = get(methodName);
+
+        if (methodList == null)
+        {
+            return null;
+        }
+
+        int l = args.length;
+        Class[] classes = new Class[l];
+
+        for(int i = 0; i < l; ++i)
+        {
+            Object arg = args[i];
+
+            /*
+             * if we are careful down below, a null argument goes in there
+             * so we can know that the null was passed to the method
+             */
+            classes[i] =
+                    arg == null ? null : arg.getClass();
+        }
+
+        return getMostSpecific(methodList, classes);
+    }
+
+    /**
+     *  simple distinguishable exception, used when
+     *  we run across ambiguous overloading
+     */
+    public static class AmbiguousException extends Exception
+    {
+    }
+
+
+    private static Method getMostSpecific(List methods, Class[] classes)
+        throws AmbiguousException
+    {
+        LinkedList<Method> applicables = getApplicables(methods, classes);
+
+        if(applicables.isEmpty())
+        {
+            return null;
+        }
+
+        if(applicables.size() == 1)
+        {
+            return 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<Method> maximals = new LinkedList<Method>();
+
+        for ( Method app : applicables )
+        {
+            Class[] appArgs = app.getParameterTypes();
+            boolean lessSpecific = false;
+
+            for ( Iterator maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); )
+            {
+                Method max = (Method) maximal.next();
+
+                switch ( moreSpecific( appArgs, max.getParameterTypes() ) )
+                {
+                    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:
+                    {
+                        /*
+                         * 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(maximals.size() > 1)
+        {
+            // We have more than one maximally specific method
+            throw new AmbiguousException();
+        }
+
+        return maximals.getFirst();
+    }
+
+    /**
+     * Determines which method signature (represented by a class array) is more
+     * specific. This defines a partial ordering on the method signatures.
+     * @param c1 first signature to compare
+     * @param c2 second signature to compare
+     * @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 moreSpecific(Class[] c1, Class[] c2)
+    {
+        boolean c1MoreSpecific = false;
+        boolean c2MoreSpecific = false;
+
+        for(int i = 0; i < c1.length; ++i)
+        {
+            if(c1[i] != c2[i])
+            {
+                c1MoreSpecific =
+                    c1MoreSpecific ||
+                    isStrictMethodInvocationConvertible(c2[i], c1[i]);
+                c2MoreSpecific =
+                    c2MoreSpecific ||
+                    isStrictMethodInvocationConvertible(c1[i], c2[i]);
+            }
+        }
+
+        if(c1MoreSpecific)
+        {
+            if(c2MoreSpecific)
+            {
+                /*
+                 *  Incomparable due to cross-assignable arguments (i.e.
+                 * foo(String, Object) vs. foo(Object, String))
+                 */
+
+                return INCOMPARABLE;
+            }
+
+            return MORE_SPECIFIC;
+        }
+
+        if(c2MoreSpecific)
+        {
+            return LESS_SPECIFIC;
+        }
+
+        /*
+         * Incomparable due to non-related arguments (i.e.
+         * foo(Runnable) vs. foo(Serializable))
+         */
+
+        return INCOMPARABLE;
+    }
+
+    /**
+     * 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<Method> getApplicables(List methods, Class[] classes)
+    {
+        LinkedList<Method> list = new LinkedList<Method>();
+
+        for ( Object method1 : methods )
+        {
+            Method method = (Method) method1;
+
+            if ( isApplicable( method, classes ) )
+            {
+                list.add( method );
+            }
+
+        }
+        return list;
+    }
+
+    /**
+     * Returns true if the supplied method is applicable to actual
+     * argument types.
+     * @param method The method to check for applicability
+     * @param classes The arguments
+     * @return true if the method applies to the parameter types
+     */
+    private static boolean isApplicable(Method method, Class[] classes)
+    {
+        Class[] methodArgs = method.getParameterTypes();
+
+        if(methodArgs.length != classes.length)
+        {
+            return false;
+        }
+
+        for(int i = 0; i < classes.length; ++i)
+        {
+            if(!isMethodInvocationConvertible(methodArgs[i], classes[i]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Determines whether a type represented by a class object is
+     * convertible to another type represented by a class object using a
+     * method invocation conversion, treating object types of primitive
+     * types as if they were primitive types (that is, a Boolean actual
+     * parameter type matches boolean primitive formal type). This behavior
+     * is because this method is used to determine applicable methods for
+     * an actual parameter list, and primitive types are represented by
+     * their object duals in reflective method calls.
+     *
+     * @param formal the formal parameter type to which the actual
+     * parameter type should be convertible
+     * @param actual the actual parameter type.
+     * @return true if either formal type is assignable from actual type,
+     * or formal is a primitive type and actual is its corresponding object
+     * type or an object type of a primitive type that can be converted to
+     * the formal type.
+     */
+    private static boolean isMethodInvocationConvertible(Class formal,
+                                                         Class actual)
+    {
+        /*
+         * if it's a null, it means the arg was null
+         */
+        if (actual == null && !formal.isPrimitive())
+        {
+            return true;
+        }
+
+        /*
+         *  Check for identity or widening reference conversion
+         */
+
+        if (actual != null && formal.isAssignableFrom(actual))
+        {
+            return true;
+        }
+
+        /*
+         * Check for boxing with widening primitive conversion. Note that
+         * actual parameters are never primitives.
+         */
+
+        if (formal.isPrimitive())
+        {
+            if(formal == Boolean.TYPE && actual == Boolean.class)
+                return true;
+            if(formal == Character.TYPE && actual == Character.class)
+                return true;
+            if(formal == Byte.TYPE && actual == Byte.class)
+                return true;
+            if(formal == Short.TYPE &&
+               (actual == Short.class || actual == Byte.class))
+                return true;
+            if(formal == Integer.TYPE &&
+               (actual == Integer.class || actual == Short.class ||
+                actual == Byte.class))
+                return true;
+            if(formal == Long.TYPE &&
+               (actual == Long.class || actual == Integer.class ||
+                actual == Short.class || actual == Byte.class))
+                return true;
+            if(formal == Float.TYPE &&
+               (actual == Float.class || actual == Long.class ||
+                actual == Integer.class || actual == Short.class ||
+                actual == Byte.class))
+                return true;
+            if(formal == Double.TYPE &&
+               (actual == Double.class || actual == Float.class ||
+                actual == Long.class || actual == Integer.class ||
+                actual == Short.class || actual == Byte.class))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines whether a type represented by a class object is
+     * convertible to another type represented by a class object using a
+     * method invocation conversion, without matching object and primitive
+     * types. This method is used to determine the more specific type when
+     * comparing signatures of methods.
+     *
+     * @param formal the formal parameter type to which the actual
+     * parameter type should be convertible
+     * @param actual the actual parameter type.
+     * @return true if either formal type is assignable from actual type,
+     * or formal and actual are both primitive types and actual can be
+     * subject to widening conversion to formal.
+     */
+    private static boolean isStrictMethodInvocationConvertible(Class formal,
+                                                               Class actual)
+    {
+        /*
+         * we shouldn't get a null into, but if so
+         */
+        if (actual == null && !formal.isPrimitive())
+        {
+            return true;
+        }
+
+        /*
+         *  Check for identity or widening reference conversion
+         */
+
+        if(formal.isAssignableFrom(actual))
+        {
+            return true;
+        }
+
+        /*
+         *  Check for widening primitive conversion.
+         */
+
+        if(formal.isPrimitive())
+        {
+            if(formal == Short.TYPE && (actual == Byte.TYPE))
+                return true;
+            if(formal == Integer.TYPE &&
+               (actual == Short.TYPE || actual == Byte.TYPE))
+                return true;
+            if(formal == Long.TYPE &&
+               (actual == Integer.TYPE || actual == Short.TYPE ||
+                actual == Byte.TYPE))
+                return true;
+            if(formal == Float.TYPE &&
+               (actual == Long.TYPE || actual == Integer.TYPE ||
+                actual == Short.TYPE || actual == Byte.TYPE))
+                return true;
+            if(formal == Double.TYPE &&
+               (actual == Float.TYPE || actual == Long.TYPE ||
+                actual == Integer.TYPE || actual == Short.TYPE ||
+                actual == Byte.TYPE))
+                return true;
+        }
+        return false;
+    }
+}

Propchange: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java
URL: http://svn.apache.org/viewvc/maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java?rev=1378767&view=auto
==============================================================================
--- maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java (added)
+++ maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java Wed Aug 29 23:24:31 2012
@@ -0,0 +1,249 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.maven.shared.utils.StringUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.WeakHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * <p>Using simple dotted expressions to extract the values from an Object instance,
+ * For example we might want to extract a value like: <code>project.build.sourceDirectory</code></p>
+ *
+ * <p>The implementation supports indexed, nested and mapped properties similar to the JSP way.</p>
+ *
+ * @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
+ * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
+ * @version $Id$
+ * @see <a href="http://struts.apache.org/1.x/struts-taglib/indexedprops.html">http://struts.apache.org/1.x/struts-taglib/indexedprops.html</a>
+ */
+public class ReflectionValueExtractor
+{
+    private static final Class[] CLASS_ARGS = new Class[0];
+
+    private static final Object[] OBJECT_ARGS = new Object[0];
+
+    /**
+     * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
+     * This approach prevents permgen space overflows due to retention of discarded
+     * classloaders.
+     */
+    private static final Map classMaps = new WeakHashMap();
+
+    /**
+     * Indexed properties pattern, ie <code>(\\w+)\\[(\\d+)\\]</code>
+     */
+    private static final Pattern INDEXED_PROPS = Pattern.compile( "(\\w+)\\[(\\d+)\\]" );
+
+    /**
+     * Indexed properties pattern, ie <code>(\\w+)\\((.+)\\)</code>
+     */
+    private static final Pattern MAPPED_PROPS = Pattern.compile( "(\\w+)\\((.+)\\)" );
+
+    private ReflectionValueExtractor()
+    {
+    }
+
+    /**
+     * <p>The implementation supports indexed, nested and mapped properties.</p>
+     *
+     * <ul>
+     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
+     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
+     * pattern, i.e. "user.addresses[1].street"</li>
+     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e. "user.addresses(myAddress).street"</li>
+     * <ul>
+     *
+     * @param expression not null expression
+     * @param root not null object
+     * @return the object defined by the expression
+     * @throws Exception if any
+     */
+    public static Object evaluate( String expression, Object root )
+            throws Exception
+    {
+        return evaluate( expression, root, true );
+    }
+
+    /**
+     * <p>The implementation supports indexed, nested and mapped properties.</p>
+     *
+     * <ul>
+     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
+     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
+     * pattern, i.e. "user.addresses[1].street"</li>
+     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e. "user.addresses(myAddress).street"</li>
+     * <ul>
+     *
+     * @param expression not null expression
+     * @param root not null object
+     * @return the object defined by the expression
+     * @throws Exception if any
+     */
+    // TODO: don't throw Exception
+    public static Object evaluate( String expression, Object root, boolean trimRootToken )
+            throws Exception
+    {
+        // if the root token refers to the supplied root object parameter, remove it.
+        if ( trimRootToken )
+        {
+            expression = expression.substring( expression.indexOf( '.' ) + 1 );
+        }
+
+        Object value = root;
+
+        // ----------------------------------------------------------------------
+        // Walk the dots and retrieve the ultimate value desired from the
+        // MavenProject instance.
+        // ----------------------------------------------------------------------
+
+        StringTokenizer parser = new StringTokenizer( expression, "." );
+
+        while ( parser.hasMoreTokens() )
+        {
+            // if we have nothing, stop now
+            if ( value == null )
+            {
+                return null;
+            }
+
+            String token = parser.nextToken();
+
+            ClassMap classMap = getClassMap( value.getClass() );
+
+            Method method;
+            Object[] localParams = OBJECT_ARGS;
+
+            // do we have an indexed property?
+            Matcher matcher = INDEXED_PROPS.matcher( token );
+            if ( matcher.find() )
+            {
+                String methodBase = StringUtils.capitalizeFirstLetter( matcher.group( 1 ) );
+                String methodName = "get" + methodBase;
+                method = classMap.findMethod( methodName, CLASS_ARGS );
+                value = method.invoke( value, OBJECT_ARGS );
+                classMap = getClassMap( value.getClass() );
+
+                if ( classMap.getCachedClass().isArray() )
+                {
+                    value = Arrays.asList( (Object[]) value );
+                    classMap = getClassMap( value.getClass() );
+                }
+
+                if ( value instanceof List )
+                {
+                    // use get method on List interface
+                    localParams = new Object[1];
+                    localParams[0] = Integer.valueOf( matcher.group( 2 ) );
+                    method = classMap.findMethod( "get", localParams );
+                }
+                else
+                {
+                    throw new Exception( "The token '" + token
+                            + "' refers to a java.util.List or an array, but the value seems is an instance of '"
+                            + value.getClass() + "'." );
+                }
+            }
+            else
+            {
+                // do we have a mapped property?
+                matcher = MAPPED_PROPS.matcher( token );
+                if ( matcher.find() )
+                {
+                    String methodBase = StringUtils.capitalizeFirstLetter( matcher.group( 1 ) );
+                    String methodName = "get" + methodBase;
+                    method = classMap.findMethod( methodName, CLASS_ARGS );
+                    value = method.invoke( value, OBJECT_ARGS );
+                    classMap = getClassMap( value.getClass() );
+
+                    if ( value instanceof Map )
+                    {
+                        // use get method on List interface
+                        localParams = new Object[1];
+                        localParams[0] = matcher.group( 2 );
+                        method = classMap.findMethod( "get", localParams );
+                    }
+                    else
+                    {
+                        throw new Exception( "The token '" + token
+                                + "' refers to a java.util.Map, but the value seems is an instance of '"
+                                + value.getClass() + "'." );
+                    }
+                }
+                else
+                {
+                    String methodBase = StringUtils.capitalizeFirstLetter( token );
+                    String methodName = "get" + methodBase;
+                    method = classMap.findMethod( methodName, CLASS_ARGS );
+
+                    if ( method == null )
+                    {
+                        // perhaps this is a boolean property??
+                        methodName = "is" + methodBase;
+
+                        method = classMap.findMethod( methodName, CLASS_ARGS );
+                    }
+                }
+            }
+
+            if ( method == null )
+            {
+                return null;
+            }
+
+            try
+            {
+                value = method.invoke( value, localParams );
+            }
+            catch ( InvocationTargetException e )
+            {
+                // catch array index issues gracefully, otherwise release
+                if ( e.getCause() instanceof IndexOutOfBoundsException )
+                {
+                    return null;
+                }
+
+                throw e;
+            }
+        }
+
+        return value;
+    }
+
+    private static ClassMap getClassMap( Class clazz )
+    {
+        ClassMap classMap = (ClassMap) classMaps.get( clazz );
+
+        if ( classMap == null )
+        {
+            classMap = new ClassMap( clazz );
+
+            classMaps.put( clazz, classMap );
+        }
+
+        return classMap;
+    }
+}

Propchange: maven/shared/trunk/maven-shared-utils/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE
URL: http://svn.apache.org/viewvc/maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE?rev=1378767&view=auto
==============================================================================
--- maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE (added)
+++ maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE Wed Aug 29 23:24:31 2012
@@ -0,0 +1,5 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed by
+ThoughtWorks (http://www.thoughtworks.com).

Propchange: maven/shared/trunk/maven-shared-utils/src/main/resources/META-INF/NOTICE
------------------------------------------------------------------------------
    svn:eol-style = native

Added: maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java
URL: http://svn.apache.org/viewvc/maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java?rev=1378767&view=auto
==============================================================================
--- maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java (added)
+++ maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java Wed Aug 29 23:24:31 2012
@@ -0,0 +1,298 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * Copyright 2001-2005 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class ReflectionValueExtractorTest
+    extends TestCase
+{
+    private Project project;
+
+    protected void setUp()
+        throws Exception
+    {
+        super.setUp();
+
+        Dependency dependency1 = new Dependency();
+        dependency1.setArtifactId("dep1");
+        Dependency dependency2 = new Dependency();
+        dependency2.setArtifactId("dep2");
+
+        project = new Project();
+        project.setModelVersion( "4.0.0" );
+        project.setGroupId( "org.apache.maven" );
+        project.setArtifactId( "maven-core" );
+        project.setName( "Maven" );
+        project.setVersion( "2.0-SNAPSHOT" );
+        project.setScm( new Scm() );
+        project.getScm().setConnection( "scm-connection" );
+        project.addDependency( dependency1 );
+        project.addDependency( dependency2 );
+        project.setBuild( new Build() );
+    }
+
+    public void testValueExtraction()
+        throws Exception
+    {
+        // ----------------------------------------------------------------------
+        // Top level values
+        // ----------------------------------------------------------------------
+
+        assertEquals( "4.0.0", ReflectionValueExtractor.evaluate( "project.modelVersion", project ) );
+
+        assertEquals( "org.apache.maven", ReflectionValueExtractor.evaluate( "project.groupId", project ) );
+
+        assertEquals( "maven-core", ReflectionValueExtractor.evaluate( "project.artifactId", project ) );
+
+        assertEquals( "Maven", ReflectionValueExtractor.evaluate( "project.name", project ) );
+
+        assertEquals( "2.0-SNAPSHOT", ReflectionValueExtractor.evaluate( "project.version", project ) );
+
+        // ----------------------------------------------------------------------
+        // SCM
+        // ----------------------------------------------------------------------
+
+        assertEquals( "scm-connection", ReflectionValueExtractor.evaluate( "project.scm.connection", project ) );
+
+        // ----------------------------------------------------------------------
+        // Dependencies
+        // ----------------------------------------------------------------------
+
+        List dependencies = (List) ReflectionValueExtractor.evaluate( "project.dependencies", project );
+
+        Assert.assertNotNull( dependencies );
+
+        Assert.assertEquals( 2, dependencies.size() );
+
+        // ----------------------------------------------------------------------
+        // Dependencies - using index notation
+        // ----------------------------------------------------------------------
+
+        // List
+        Dependency dependency = (Dependency)ReflectionValueExtractor.evaluate( "project.dependencies[0]", project );
+
+        Assert.assertNotNull( dependency );
+
+        Assert.assertTrue( "dep1".equals(dependency.getArtifactId()) );
+
+        String artifactId = (String)ReflectionValueExtractor.evaluate( "project.dependencies[1].artifactId", project );
+
+        Assert.assertTrue( "dep2".equals(artifactId) );
+
+        // Array
+
+        dependency = (Dependency)ReflectionValueExtractor.evaluate( "project.dependenciesAsArray[0]", project );
+
+        Assert.assertNotNull( dependency );
+
+        Assert.assertTrue( "dep1".equals(dependency.getArtifactId()) );
+
+        artifactId = (String)ReflectionValueExtractor.evaluate( "project.dependenciesAsArray[1].artifactId", project );
+
+        Assert.assertTrue( "dep2".equals(artifactId) );
+
+        // Map
+
+        dependency = (Dependency)ReflectionValueExtractor.evaluate( "project.dependenciesAsMap(dep1)", project );
+
+        Assert.assertNotNull( dependency );
+
+        Assert.assertTrue( "dep1".equals(dependency.getArtifactId()) );
+
+        artifactId = (String)ReflectionValueExtractor.evaluate( "project.dependenciesAsMap(dep2).artifactId", project );
+
+        Assert.assertTrue( "dep2".equals(artifactId) );
+
+        // ----------------------------------------------------------------------
+        // Build
+        // ----------------------------------------------------------------------
+
+        Build build = (Build) ReflectionValueExtractor.evaluate( "project.build", project );
+
+        Assert.assertNotNull( build );
+    }
+
+    public void testValueExtractorWithAInvalidExpression()
+        throws Exception
+    {
+        Assert.assertNull( ReflectionValueExtractor.evaluate( "project.foo", project ) );
+        Assert.assertNull( ReflectionValueExtractor.evaluate( "project.dependencies[10]", project ) );
+        Assert.assertNull( ReflectionValueExtractor.evaluate( "project.dependencies[0].foo", project ) );
+    }
+
+    public static class Project
+    {
+        private String modelVersion;
+
+        private String groupId;
+
+        private Scm scm;
+
+        private List dependencies = new ArrayList();
+
+        private Build build;
+
+        private String artifactId;
+
+        private String name;
+
+        private String version;
+
+        public void setModelVersion( String modelVersion )
+        {
+            this.modelVersion = modelVersion;
+        }
+
+        public void setGroupId( String groupId )
+        {
+            this.groupId = groupId;
+        }
+
+        public void setScm( Scm scm )
+        {
+            this.scm = scm;
+        }
+
+        public void addDependency( Dependency dependency )
+        {
+            this.dependencies.add( dependency );
+        }
+
+        public void setBuild( Build build )
+        {
+            this.build = build;
+        }
+
+        public void setArtifactId( String artifactId )
+        {
+            this.artifactId = artifactId;
+        }
+
+        public void setName( String name )
+        {
+            this.name = name;
+        }
+
+        public void setVersion( String version )
+        {
+            this.version = version;
+        }
+
+        public Scm getScm()
+        {
+            return scm;
+        }
+
+        public String getModelVersion()
+        {
+            return modelVersion;
+        }
+
+        public String getGroupId()
+        {
+            return groupId;
+        }
+
+        public List getDependencies()
+        {
+            return dependencies;
+        }
+
+        public Build getBuild()
+        {
+            return build;
+        }
+
+        public String getArtifactId()
+        {
+            return artifactId;
+        }
+
+        public String getName()
+        {
+            return name;
+        }
+
+        public String getVersion()
+        {
+            return version;
+        }
+
+        public Dependency[] getDependenciesAsArray()
+        {
+            return (Dependency[]) getDependencies().toArray( new Dependency[0]);
+        }
+
+        public Map getDependenciesAsMap()
+        {
+            Map ret = new HashMap();
+            for ( Iterator it = getDependencies().iterator(); it.hasNext();)
+            {
+                Dependency dep = (Dependency)it.next();
+                ret.put( dep.getArtifactId(), dep );
+            }
+            return ret;
+        }
+    }
+
+    public static class Build
+    {
+
+    }
+
+    public static class Dependency
+    {
+        private String artifactId;
+
+        public String getArtifactId()
+        {
+            return artifactId;
+        }
+
+        public void setArtifactId(String id)
+        {
+            artifactId = id;
+        }
+    }
+
+    public static class Scm
+    {
+        private String connection;
+
+        public void setConnection( String connection )
+        {
+            this.connection = connection;
+        }
+
+        public String getConnection()
+        {
+            return connection;
+        }
+    }
+}

Propchange: maven/shared/trunk/maven-shared-utils/src/test/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native