Home | History | Annotate | Download | only in resolution
      1 /*
      2  * Copyright 2016 Federico Tomassetti
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.github.javaparser.symbolsolver.resolution;
     18 
     19 import com.github.javaparser.resolution.MethodAmbiguityException;
     20 import com.github.javaparser.resolution.MethodUsage;
     21 import com.github.javaparser.resolution.declarations.*;
     22 import com.github.javaparser.resolution.types.*;
     23 import com.github.javaparser.symbolsolver.core.resolution.Context;
     24 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserAnonymousClassDeclaration;
     25 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration;
     26 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserEnumDeclaration;
     27 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration;
     28 import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserMethodDeclaration;
     29 import com.github.javaparser.symbolsolver.javassistmodel.JavassistClassDeclaration;
     30 import com.github.javaparser.symbolsolver.javassistmodel.JavassistEnumDeclaration;
     31 import com.github.javaparser.symbolsolver.javassistmodel.JavassistInterfaceDeclaration;
     32 import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
     33 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
     34 import com.github.javaparser.symbolsolver.model.typesystem.*;
     35 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionClassDeclaration;
     36 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionEnumDeclaration;
     37 import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionInterfaceDeclaration;
     38 
     39 import java.util.*;
     40 import java.util.stream.Collectors;
     41 
     42 /**
     43  * @author Federico Tomassetti
     44  */
     45 public class MethodResolutionLogic {
     46 
     47     private static List<ResolvedType> groupVariadicParamValues(List<ResolvedType> argumentsTypes, int startVariadic, ResolvedType variadicType) {
     48         List<ResolvedType> res = new ArrayList<>(argumentsTypes.subList(0, startVariadic));
     49         List<ResolvedType> variadicValues = argumentsTypes.subList(startVariadic, argumentsTypes.size());
     50         if (variadicValues.isEmpty()) {
     51             // TODO if there are no variadic values we should default to the bound of the formal type
     52             res.add(variadicType);
     53         } else {
     54             ResolvedType componentType = findCommonType(variadicValues);
     55             res.add(new ResolvedArrayType(componentType));
     56         }
     57         return res;
     58     }
     59 
     60     private static ResolvedType findCommonType(List<ResolvedType> variadicValues) {
     61         if (variadicValues.isEmpty()) {
     62             throw new IllegalArgumentException();
     63         }
     64         // TODO implement this decently
     65         return variadicValues.get(0);
     66     }
     67 
     68     public static boolean isApplicable(ResolvedMethodDeclaration method, String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) {
     69         return isApplicable(method, name, argumentsTypes, typeSolver, false);
     70     }
     71 
     72     private static boolean isApplicable(ResolvedMethodDeclaration method, String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver, boolean withWildcardTolerance) {
     73         if (!method.getName().equals(name)) {
     74             return false;
     75         }
     76         if (method.hasVariadicParameter()) {
     77             int pos = method.getNumberOfParams() - 1;
     78             if (method.getNumberOfParams() == argumentsTypes.size()) {
     79                 // check if the last value is directly assignable as an array
     80                 ResolvedType expectedType = method.getLastParam().getType();
     81                 ResolvedType actualType = argumentsTypes.get(pos);
     82                 if (!expectedType.isAssignableBy(actualType)) {
     83                     for (ResolvedTypeParameterDeclaration tp : method.getTypeParameters()) {
     84                         expectedType = replaceTypeParam(expectedType, tp, typeSolver);
     85                     }
     86                     if (!expectedType.isAssignableBy(actualType)) {
     87                         if (actualType.isArray() && expectedType.isAssignableBy(actualType.asArrayType().getComponentType())) {
     88                             argumentsTypes.set(pos, actualType.asArrayType().getComponentType());
     89                         } else {
     90                             argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, method.getLastParam().getType());
     91                         }
     92                     }
     93                 } // else it is already assignable, nothing to do
     94             } else {
     95                 if (pos > argumentsTypes.size()) {
     96                   return false;
     97                 }
     98                 argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, method.getLastParam().getType());
     99             }
    100         }
    101 
    102         if (method.getNumberOfParams() != argumentsTypes.size()) {
    103             return false;
    104         }
    105         Map<String, ResolvedType> matchedParameters = new HashMap<>();
    106         boolean needForWildCardTolerance = false;
    107         for (int i = 0; i < method.getNumberOfParams(); i++) {
    108             ResolvedType expectedType = method.getParam(i).getType();
    109             ResolvedType actualType = argumentsTypes.get(i);
    110             if ((expectedType.isTypeVariable() && !(expectedType.isWildcard())) && expectedType.asTypeParameter().declaredOnMethod()) {
    111                 matchedParameters.put(expectedType.asTypeParameter().getName(), actualType);
    112                 continue;
    113             }
    114             boolean isAssignableWithoutSubstitution = expectedType.isAssignableBy(actualType) ||
    115                     (method.getParam(i).isVariadic() && new ResolvedArrayType(expectedType).isAssignableBy(actualType));
    116             if (!isAssignableWithoutSubstitution && expectedType.isReferenceType() && actualType.isReferenceType()) {
    117                 isAssignableWithoutSubstitution = isAssignableMatchTypeParameters(
    118                         expectedType.asReferenceType(),
    119                         actualType.asReferenceType(),
    120                         matchedParameters);
    121             }
    122             if (!isAssignableWithoutSubstitution) {
    123                 List<ResolvedTypeParameterDeclaration> typeParameters = method.getTypeParameters();
    124                 typeParameters.addAll(method.declaringType().getTypeParameters());
    125                 for (ResolvedTypeParameterDeclaration tp : typeParameters) {
    126                     expectedType = replaceTypeParam(expectedType, tp, typeSolver);
    127                 }
    128 
    129                 if (!expectedType.isAssignableBy(actualType)) {
    130                     if (actualType.isWildcard() && withWildcardTolerance && !expectedType.isPrimitive()) {
    131                         needForWildCardTolerance = true;
    132                         continue;
    133                     }
    134                     if (method.hasVariadicParameter() && i == method.getNumberOfParams() - 1) {
    135                         if (new ResolvedArrayType(expectedType).isAssignableBy(actualType)) {
    136                             continue;
    137                         }
    138                     }
    139                     return false;
    140                 }
    141             }
    142         }
    143         return !withWildcardTolerance || needForWildCardTolerance;
    144     }
    145 
    146     public static boolean isAssignableMatchTypeParameters(ResolvedType expected, ResolvedType actual,
    147                                                           Map<String, ResolvedType> matchedParameters) {
    148         if (expected.isReferenceType() && actual.isReferenceType()) {
    149             return isAssignableMatchTypeParameters(expected.asReferenceType(), actual.asReferenceType(), matchedParameters);
    150         } else if (expected.isTypeVariable()) {
    151             matchedParameters.put(expected.asTypeParameter().getName(), actual);
    152             return true;
    153         } else {
    154             throw new UnsupportedOperationException(expected.getClass().getCanonicalName() + " " + actual.getClass().getCanonicalName());
    155         }
    156     }
    157 
    158     public static boolean isAssignableMatchTypeParameters(ResolvedReferenceType expected, ResolvedReferenceType actual,
    159                                                           Map<String, ResolvedType> matchedParameters) {
    160         if (actual.getQualifiedName().equals(expected.getQualifiedName())) {
    161             return isAssignableMatchTypeParametersMatchingQName(expected, actual, matchedParameters);
    162         } else {
    163             List<ResolvedReferenceType> ancestors = actual.getAllAncestors();
    164             for (ResolvedReferenceType ancestor : ancestors) {
    165                 if (isAssignableMatchTypeParametersMatchingQName(expected, ancestor, matchedParameters)) {
    166                     return true;
    167                 }
    168             }
    169         }
    170         return false;
    171     }
    172 
    173     private static boolean isAssignableMatchTypeParametersMatchingQName(ResolvedReferenceType expected, ResolvedReferenceType actual,
    174                                                                         Map<String, ResolvedType> matchedParameters) {
    175 
    176         if (!expected.getQualifiedName().equals(actual.getQualifiedName())) {
    177             return false;
    178         }
    179         if (expected.typeParametersValues().size() != actual.typeParametersValues().size()) {
    180             throw new UnsupportedOperationException();
    181             //return true;
    182         }
    183         for (int i = 0; i < expected.typeParametersValues().size(); i++) {
    184             ResolvedType expectedParam = expected.typeParametersValues().get(i);
    185             ResolvedType actualParam = actual.typeParametersValues().get(i);
    186 
    187             // In the case of nested parameterizations eg. List<R> <-> List<Integer>
    188             // we should peel off one layer and ensure R <-> Integer
    189             if (expectedParam.isReferenceType() && actualParam.isReferenceType()){
    190                 ResolvedReferenceType r1 = expectedParam.asReferenceType();
    191                 ResolvedReferenceType r2 = actualParam.asReferenceType();
    192 
    193                 return isAssignableMatchTypeParametersMatchingQName(r1, r2, matchedParameters);
    194             }
    195 
    196             if (expectedParam.isTypeVariable()) {
    197                 String expectedParamName = expectedParam.asTypeParameter().getName();
    198                 if (!actualParam.isTypeVariable() || !actualParam.asTypeParameter().getName().equals(expectedParamName)) {
    199                     return matchTypeVariable(expectedParam.asTypeVariable(), actualParam, matchedParameters);
    200                 }
    201             } else if (expectedParam.isReferenceType()) {
    202                 if (actualParam.isTypeVariable()) {
    203                     return matchTypeVariable(actualParam.asTypeVariable(), expectedParam, matchedParameters);
    204                 } else if (!expectedParam.equals(actualParam)) {
    205                     return false;
    206                 }
    207             } else if (expectedParam.isWildcard()) {
    208                 if (expectedParam.asWildcard().isExtends()) {
    209                     return isAssignableMatchTypeParameters(expectedParam.asWildcard().getBoundedType(), actual, matchedParameters);
    210                 }
    211                 // TODO verify super bound
    212                 return true;
    213             } else {
    214                 throw new UnsupportedOperationException(expectedParam.describe());
    215             }
    216         }
    217         return true;
    218     }
    219 
    220     private static boolean matchTypeVariable(ResolvedTypeVariable typeVariable, ResolvedType type, Map<String, ResolvedType> matchedParameters) {
    221         String typeParameterName = typeVariable.asTypeParameter().getName();
    222         if (matchedParameters.containsKey(typeParameterName)) {
    223             ResolvedType matchedParameter = matchedParameters.get(typeParameterName);
    224             if (matchedParameter.isAssignableBy(type)) {
    225                 return true;
    226             } else if (type.isAssignableBy(matchedParameter)) {
    227                 // update matchedParameters to contain the more general type
    228                 matchedParameters.put(typeParameterName, type);
    229                 return true;
    230             }
    231             return false;
    232         } else {
    233             matchedParameters.put(typeParameterName, type);
    234         }
    235         return true;
    236     }
    237 
    238     public static ResolvedType replaceTypeParam(ResolvedType type, ResolvedTypeParameterDeclaration tp, TypeSolver typeSolver) {
    239         if (type.isTypeVariable()) {
    240             if (type.describe().equals(tp.getName())) {
    241                 List<ResolvedTypeParameterDeclaration.Bound> bounds = tp.getBounds();
    242                 if (bounds.size() > 1) {
    243                     throw new UnsupportedOperationException();
    244                 } else if (bounds.size() == 1) {
    245                     return bounds.get(0).getType();
    246                 } else {
    247                     return new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver);
    248                 }
    249             }
    250             return type;
    251         } else if (type.isPrimitive()) {
    252             return type;
    253         } else if (type.isArray()) {
    254             return new ResolvedArrayType(replaceTypeParam(type.asArrayType().getComponentType(), tp, typeSolver));
    255         } else if (type.isReferenceType()) {
    256             ResolvedReferenceType result = type.asReferenceType();
    257             result = result.transformTypeParameters(typeParam -> replaceTypeParam(typeParam, tp, typeSolver)).asReferenceType();
    258             return result;
    259         } else if (type.isWildcard()) {
    260             if (type.describe().equals(tp.getName())) {
    261                 List<ResolvedTypeParameterDeclaration.Bound> bounds = tp.getBounds();
    262                 if (bounds.size() > 1) {
    263                     throw new UnsupportedOperationException();
    264                 } else if (bounds.size() == 1) {
    265                     return bounds.get(0).getType();
    266                 } else {
    267                     return new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver);
    268                 }
    269             }
    270             return type;
    271         } else {
    272             throw new UnsupportedOperationException("Replacing " + type + ", param " + tp + " with " + type.getClass().getCanonicalName());
    273         }
    274     }
    275 
    276     public static boolean isApplicable(MethodUsage method, String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) {
    277         if (!method.getName().equals(name)) {
    278             return false;
    279         }
    280         // TODO Consider varargs
    281         if (method.getNoParams() != argumentsTypes.size()) {
    282             return false;
    283         }
    284         for (int i = 0; i < method.getNoParams(); i++) {
    285             ResolvedType expectedType = method.getParamType(i);
    286             ResolvedType expectedTypeWithoutSubstitutions = expectedType;
    287             ResolvedType expectedTypeWithInference = method.getParamType(i);
    288             ResolvedType actualType = argumentsTypes.get(i);
    289 
    290             List<ResolvedTypeParameterDeclaration> typeParameters = method.getDeclaration().getTypeParameters();
    291             typeParameters.addAll(method.declaringType().getTypeParameters());
    292 
    293             if (expectedType.describe().equals(actualType.describe())){
    294                 return true;
    295             }
    296 
    297             Map<ResolvedTypeParameterDeclaration, ResolvedType> derivedValues = new HashMap<>();
    298             for (int j = 0; j < method.getParamTypes().size(); j++) {
    299                 ResolvedParameterDeclaration parameter = method.getDeclaration().getParam(i);
    300                 ResolvedType parameterType = parameter.getType();
    301                 if (parameter.isVariadic()) {
    302                     parameterType = parameterType.asArrayType().getComponentType();
    303                 }
    304                 inferTypes(argumentsTypes.get(j), parameterType, derivedValues);
    305             }
    306 
    307             for (Map.Entry<ResolvedTypeParameterDeclaration, ResolvedType> entry : derivedValues.entrySet()){
    308                 ResolvedTypeParameterDeclaration tp = entry.getKey();
    309                 expectedTypeWithInference = expectedTypeWithInference.replaceTypeVariables(tp, entry.getValue());
    310             }
    311 
    312             for (ResolvedTypeParameterDeclaration tp : typeParameters) {
    313                 if (tp.getBounds().isEmpty()) {
    314                     //expectedType = expectedType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
    315                     expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver)));
    316                 } else if (tp.getBounds().size() == 1) {
    317                     ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0);
    318                     if (bound.isExtends()) {
    319                         //expectedType = expectedType.replaceTypeVariables(tp.getName(), bound.getType());
    320                         expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(bound.getType()));
    321                     } else {
    322                         //expectedType = expectedType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
    323                         expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.superBound(bound.getType()));
    324                     }
    325                 } else {
    326                     throw new UnsupportedOperationException();
    327                 }
    328             }
    329             ResolvedType expectedType2 = expectedTypeWithoutSubstitutions;
    330             for (ResolvedTypeParameterDeclaration tp : typeParameters) {
    331                 if (tp.getBounds().isEmpty()) {
    332                     expectedType2 = expectedType2.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
    333                 } else if (tp.getBounds().size() == 1) {
    334                     ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0);
    335                     if (bound.isExtends()) {
    336                         expectedType2 = expectedType2.replaceTypeVariables(tp, bound.getType());
    337                     } else {
    338                         expectedType2 = expectedType2.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
    339                     }
    340                 } else {
    341                     throw new UnsupportedOperationException();
    342                 }
    343             }
    344             if (!expectedType.isAssignableBy(actualType)
    345                     && !expectedType2.isAssignableBy(actualType)
    346                     && !expectedTypeWithInference.isAssignableBy(actualType)
    347                     && !expectedTypeWithoutSubstitutions.isAssignableBy(actualType)) {
    348                 return false;
    349             }
    350         }
    351         return true;
    352     }
    353 
    354     private static List<ResolvedMethodDeclaration> getMethodsWithoutDuplicates(List<ResolvedMethodDeclaration> methods) {
    355         Set<ResolvedMethodDeclaration> s = new TreeSet<>((m1, m2) -> {
    356             if (m1 instanceof JavaParserMethodDeclaration && m2 instanceof JavaParserMethodDeclaration &&
    357                     ((JavaParserMethodDeclaration) m1).getWrappedNode().equals(((JavaParserMethodDeclaration) m2).getWrappedNode())) {
    358                 return 0;
    359             }
    360             return 1;
    361         });
    362         s.addAll(methods);
    363         List<ResolvedMethodDeclaration> res = new ArrayList<>();
    364         Set<String> usedSignatures = new HashSet<>();
    365         for (ResolvedMethodDeclaration md : methods) {
    366             String signature = md.getQualifiedSignature();
    367             if (!usedSignatures.contains(signature)) {
    368                 usedSignatures.add(signature);
    369                 res.add(md);
    370             }
    371         }
    372         return res;
    373     }
    374 
    375     /**
    376      * @param methods        we expect the methods to be ordered such that inherited methods are later in the list
    377      * @param name
    378      * @param argumentsTypes
    379      * @param typeSolver
    380      * @return
    381      */
    382     public static SymbolReference<ResolvedMethodDeclaration> findMostApplicable(List<ResolvedMethodDeclaration> methods,
    383                                                                                 String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) {
    384         SymbolReference<ResolvedMethodDeclaration> res = findMostApplicable(methods, name, argumentsTypes, typeSolver, false);
    385         if (res.isSolved()) {
    386             return res;
    387         }
    388         return findMostApplicable(methods, name, argumentsTypes, typeSolver, true);
    389     }
    390 
    391     public static SymbolReference<ResolvedMethodDeclaration> findMostApplicable(List<ResolvedMethodDeclaration> methods,
    392                                                                                 String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver, boolean wildcardTolerance) {
    393         List<ResolvedMethodDeclaration> applicableMethods = getMethodsWithoutDuplicates(methods).stream().filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver, wildcardTolerance)).collect(Collectors.toList());
    394         if (applicableMethods.isEmpty()) {
    395             return SymbolReference.unsolved(ResolvedMethodDeclaration.class);
    396         }
    397 
    398         if (applicableMethods.size() > 1) {
    399           List<Integer> nullParamIndexes = new ArrayList<>();
    400           for (int i = 0; i < argumentsTypes.size(); i++) {
    401             if (argumentsTypes.get(i).isNull()) {
    402               nullParamIndexes.add(i);
    403             }
    404           }
    405           if (!nullParamIndexes.isEmpty()) {
    406             // remove method with array param if a non array exists and arg is null
    407             Set<ResolvedMethodDeclaration> removeCandidates = new HashSet<>();
    408             for (Integer nullParamIndex: nullParamIndexes) {
    409               for (ResolvedMethodDeclaration methDecl: applicableMethods) {
    410                 if (methDecl.getParam(nullParamIndex.intValue()).getType().isArray()) {
    411                   removeCandidates.add(methDecl);
    412                 }
    413               }
    414             }
    415             if (!removeCandidates.isEmpty() && removeCandidates.size() < applicableMethods.size()) {
    416               applicableMethods.removeAll(removeCandidates);
    417             }
    418           }
    419         }
    420         if (applicableMethods.size() == 1) {
    421             return SymbolReference.solved(applicableMethods.get(0));
    422         } else {
    423             ResolvedMethodDeclaration winningCandidate = applicableMethods.get(0);
    424             ResolvedMethodDeclaration other = null;
    425             boolean possibleAmbiguity = false;
    426             for (int i = 1; i < applicableMethods.size(); i++) {
    427                 other = applicableMethods.get(i);
    428                 if (isMoreSpecific(winningCandidate, other, argumentsTypes, typeSolver)) {
    429                     possibleAmbiguity = false;
    430                 } else if (isMoreSpecific(other, winningCandidate, argumentsTypes, typeSolver)) {
    431                     possibleAmbiguity = false;
    432                     winningCandidate = other;
    433                 } else {
    434                     if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) {
    435                         possibleAmbiguity = true;
    436                     } else {
    437                         // we expect the methods to be ordered such that inherited methods are later in the list
    438                     }
    439                 }
    440             }
    441             if (possibleAmbiguity) {
    442               // pick the first exact match if it exists
    443               if (!isExactMatch(winningCandidate, argumentsTypes)) {
    444                 if (isExactMatch(other, argumentsTypes)) {
    445                   winningCandidate = other;
    446                 } else {
    447                   throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method: " + winningCandidate + ", " + other);
    448                 }
    449               }
    450             }
    451             return SymbolReference.solved(winningCandidate);
    452         }
    453     }
    454 
    455     protected static boolean isExactMatch(ResolvedMethodLikeDeclaration method, List<ResolvedType> argumentsTypes) {
    456       for (int i = 0; i < method.getNumberOfParams(); i++) {
    457         if (!method.getParam(i).getType().equals(argumentsTypes.get(i))) {
    458           return false;
    459         }
    460       }
    461       return true;
    462     }
    463 
    464     private static boolean isMoreSpecific(ResolvedMethodDeclaration methodA, ResolvedMethodDeclaration methodB,
    465                                           List<ResolvedType> argumentTypes, TypeSolver typeSolver) {
    466         boolean oneMoreSpecificFound = false;
    467         if (methodA.getNumberOfParams() < methodB.getNumberOfParams()) {
    468             return true;
    469         }
    470         if (methodA.getNumberOfParams() > methodB.getNumberOfParams()) {
    471             return false;
    472         }
    473         for (int i = 0; i < methodA.getNumberOfParams(); i++) {
    474             ResolvedType tdA = methodA.getParam(i).getType();
    475             ResolvedType tdB = methodB.getParam(i).getType();
    476             // B is more specific
    477             if (tdB.isAssignableBy(tdA) && !tdA.isAssignableBy(tdB)) {
    478                 oneMoreSpecificFound = true;
    479             }
    480             // A is more specific
    481             if (tdA.isAssignableBy(tdB) && !tdB.isAssignableBy(tdA)) {
    482                 return false;
    483             }
    484         }
    485 
    486         if (!oneMoreSpecificFound) {
    487             int lastIndex = argumentTypes.size() - 1;
    488 
    489             if (methodA.hasVariadicParameter() && !methodB.hasVariadicParameter()) {
    490                 // if the last argument is an array then m1 is more specific
    491                 if (argumentTypes.get(lastIndex).isArray()) {
    492                     return true;
    493                 }
    494 
    495                 if (!argumentTypes.get(lastIndex).isArray()) {
    496                     return false;
    497                 }
    498             }
    499             if (!methodA.hasVariadicParameter() && methodB.hasVariadicParameter()) {
    500                 // if the last argument is an array and m1 is not variadic then
    501                 // it is not more specific
    502                 if (argumentTypes.get(lastIndex).isArray()) {
    503                     return false;
    504                 }
    505 
    506                 if (!argumentTypes.get(lastIndex).isArray()) {
    507                     return true;
    508                 }
    509             }
    510         }
    511 
    512         return oneMoreSpecificFound;
    513     }
    514 
    515     private static boolean isMoreSpecific(MethodUsage methodA, MethodUsage methodB, TypeSolver typeSolver) {
    516         boolean oneMoreSpecificFound = false;
    517         for (int i = 0; i < methodA.getNoParams(); i++) {
    518             ResolvedType tdA = methodA.getParamType(i);
    519             ResolvedType tdB = methodB.getParamType(i);
    520 
    521             boolean aIsAssignableByB = tdA.isAssignableBy(tdB);
    522             boolean bIsAssignableByA = tdB.isAssignableBy(tdA);
    523 
    524             // B is more specific
    525             if (bIsAssignableByA && !aIsAssignableByB) {
    526                 oneMoreSpecificFound = true;
    527             }
    528             // A is more specific
    529             if (aIsAssignableByB && !bIsAssignableByA) {
    530                 return false;
    531             }
    532         }
    533         return oneMoreSpecificFound;
    534     }
    535 
    536     public static Optional<MethodUsage> findMostApplicableUsage(List<MethodUsage> methods, String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) {
    537         List<MethodUsage> applicableMethods = methods.stream().filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver)).collect(Collectors.toList());
    538 
    539         if (applicableMethods.isEmpty()) {
    540             return Optional.empty();
    541         }
    542         if (applicableMethods.size() == 1) {
    543             return Optional.of(applicableMethods.get(0));
    544         } else {
    545             MethodUsage winningCandidate = applicableMethods.get(0);
    546             for (int i = 1; i < applicableMethods.size(); i++) {
    547                 MethodUsage other = applicableMethods.get(i);
    548                 if (isMoreSpecific(winningCandidate, other, typeSolver)) {
    549                     // nothing to do
    550                 } else if (isMoreSpecific(other, winningCandidate, typeSolver)) {
    551                     winningCandidate = other;
    552                 } else {
    553                     if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) {
    554                         if (!areOverride(winningCandidate, other)) {
    555                             throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method: " + winningCandidate + ", " + other + ". First declared in " + winningCandidate.declaringType().getQualifiedName());
    556                         }
    557                     } else {
    558                         // we expect the methods to be ordered such that inherited methods are later in the list
    559                         //throw new UnsupportedOperationException();
    560                     }
    561                 }
    562             }
    563             return Optional.of(winningCandidate);
    564         }
    565     }
    566 
    567     private static boolean areOverride(MethodUsage winningCandidate, MethodUsage other) {
    568         if (!winningCandidate.getName().equals(other.getName())) {
    569             return false;
    570         }
    571         if (winningCandidate.getNoParams() != other.getNoParams()) {
    572             return false;
    573         }
    574         for (int i = 0; i < winningCandidate.getNoParams(); i++) {
    575             if (!winningCandidate.getParamTypes().get(i).equals(other.getParamTypes().get(i))) {
    576                 return false;
    577             }
    578         }
    579         return true;
    580     }
    581 
    582     public static SymbolReference<ResolvedMethodDeclaration> solveMethodInType(ResolvedTypeDeclaration typeDeclaration,
    583                                                                                String name, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) {
    584         return solveMethodInType(typeDeclaration, name, argumentsTypes, false, typeSolver);
    585     }
    586 
    587         /**
    588          * Replace TypeDeclaration.solveMethod
    589          *
    590          * @param typeDeclaration
    591          * @param name
    592          * @param argumentsTypes
    593          * @param staticOnly
    594          * @return
    595          */
    596     public static SymbolReference<ResolvedMethodDeclaration> solveMethodInType(ResolvedTypeDeclaration typeDeclaration,
    597                                                                                String name, List<ResolvedType> argumentsTypes, boolean staticOnly,
    598                                                                                TypeSolver typeSolver) {
    599         if (typeDeclaration instanceof JavaParserClassDeclaration) {
    600             Context ctx = ((JavaParserClassDeclaration) typeDeclaration).getContext();
    601             return ctx.solveMethod(name, argumentsTypes, staticOnly, typeSolver);
    602         }
    603         if (typeDeclaration instanceof JavaParserInterfaceDeclaration) {
    604             Context ctx = ((JavaParserInterfaceDeclaration) typeDeclaration).getContext();
    605             return ctx.solveMethod(name, argumentsTypes, staticOnly, typeSolver);
    606         }
    607         if (typeDeclaration instanceof JavaParserEnumDeclaration) {
    608             if (name.equals("values") && argumentsTypes.isEmpty()) {
    609                 return SymbolReference.solved(new JavaParserEnumDeclaration.ValuesMethod((JavaParserEnumDeclaration) typeDeclaration, typeSolver));
    610             }
    611             Context ctx = ((JavaParserEnumDeclaration) typeDeclaration).getContext();
    612             return ctx.solveMethod(name, argumentsTypes, staticOnly, typeSolver);
    613         }
    614         if (typeDeclaration instanceof JavaParserAnonymousClassDeclaration) {
    615         	Context ctx = ((JavaParserAnonymousClassDeclaration) typeDeclaration).getContext();
    616             return ctx.solveMethod(name, argumentsTypes, staticOnly, typeSolver);
    617         }
    618         if (typeDeclaration instanceof ReflectionClassDeclaration) {
    619             return ((ReflectionClassDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    620         }
    621         if (typeDeclaration instanceof ReflectionInterfaceDeclaration) {
    622           return ((ReflectionInterfaceDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    623         }
    624           if (typeDeclaration instanceof ReflectionEnumDeclaration) {
    625             return ((ReflectionEnumDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    626         }
    627         if (typeDeclaration instanceof JavassistInterfaceDeclaration) {
    628             return ((JavassistInterfaceDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    629         }
    630         if (typeDeclaration instanceof JavassistClassDeclaration) {
    631           return ((JavassistClassDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    632         }
    633           if (typeDeclaration instanceof JavassistEnumDeclaration) {
    634             return ((JavassistEnumDeclaration) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly);
    635         }
    636         throw new UnsupportedOperationException(typeDeclaration.getClass().getCanonicalName());
    637     }
    638 
    639     private static void inferTypes(ResolvedType source, ResolvedType target, Map<ResolvedTypeParameterDeclaration, ResolvedType> mappings) {
    640 
    641 
    642         if (source.equals(target)) {
    643             return;
    644         }
    645         if (source.isReferenceType() && target.isReferenceType()) {
    646             ResolvedReferenceType sourceRefType = source.asReferenceType();
    647             ResolvedReferenceType targetRefType = target.asReferenceType();
    648             if (sourceRefType.getQualifiedName().equals(targetRefType.getQualifiedName())) {
    649                 if (!sourceRefType.isRawType() && !targetRefType.isRawType()) {
    650                     for (int i = 0; i < sourceRefType.typeParametersValues().size(); i++) {
    651                         inferTypes(sourceRefType.typeParametersValues().get(i), targetRefType.typeParametersValues().get(i), mappings);
    652                     }
    653                 }
    654             }
    655             return;
    656         }
    657         if (source.isReferenceType() && target.isWildcard()) {
    658             if (target.asWildcard().isBounded()) {
    659                 inferTypes(source, target.asWildcard().getBoundedType(), mappings);
    660                 return;
    661             }
    662             return;
    663         }
    664         if (source.isWildcard() && target.isWildcard()) {
    665             return;
    666         }
    667         if (source.isReferenceType() && target.isTypeVariable()) {
    668             mappings.put(target.asTypeParameter(), source);
    669             return;
    670         }
    671 
    672         if (source.isWildcard() && target.isReferenceType()){
    673             if (source.asWildcard().isBounded()){
    674                 inferTypes(source.asWildcard().getBoundedType(), target, mappings);
    675             }
    676             return;
    677         }
    678 
    679         if (source.isWildcard() && target.isTypeVariable()) {
    680             mappings.put(target.asTypeParameter(), source);
    681             return;
    682         }
    683         if (source.isTypeVariable() && target.isTypeVariable()) {
    684             mappings.put(target.asTypeParameter(), source);
    685             return;
    686         }
    687         if (source.isPrimitive() || target.isPrimitive()) {
    688             return;
    689         }
    690         if (source.isNull()) {
    691             return;
    692         }
    693     }
    694 
    695 
    696 }
    697