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