| package org.apache.maven.shared.utils.introspection; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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$ |
| */ |
| 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. |
| */ |
| private final 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 |
| */ |
| 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 |
| */ |
| 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/> |
| * <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 |
| */ |
| Method find( String methodName, Object... args ) |
| throws AmbiguousException |
| { |
| List<Method> 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 |
| */ |
| static class AmbiguousException |
| extends Exception |
| { |
| |
| private static final long serialVersionUID = 751688436639650618L; |
| } |
| |
| |
| private static Method getMostSpecific( List<Method> 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<Method> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); ) |
| { |
| Method max = 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; |
| |
| default: |
| } |
| } |
| |
| 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<Method> methods, Class<?>... classes ) |
| { |
| LinkedList<Method> list = new LinkedList<Method>(); |
| |
| for ( Method method : methods ) |
| { |
| 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; |
| } |
| } |