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:jvan...@apache.org">Jason van Zyl</a> + * @author <a href="mailto:b...@werken.com">Bob McWhirter</a> + * @author <a href="mailto:szege...@freemail.hu">Attila Szegedi</a> + * @author <a href="mailto:ge...@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:jvan...@apache.org">Jason van Zyl</a> + * @author <a href="mailto:b...@werken.com">Bob McWhirter</a> + * @author <a href="mailto:christoph.r...@dlr.de">Christoph Reck</a> + * @author <a href="mailto:ge...@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:szege...@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:ja...@maven.org">Jason van Zyl </a> + * @author <a href="mailto:vincent.sive...@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:ja...@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