All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.javaparser.symbolsolver.resolution.MethodResolutionLogic Maven / Gradle / Ivy

There is a newer version: 3.26.3
Show newest version
/*
 * Copyright 2016 Federico Tomassetti
 *
 * 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.
 */

package com.github.javaparser.symbolsolver.resolution;

import com.github.javaparser.resolution.MethodAmbiguityException;
import com.github.javaparser.resolution.MethodUsage;
import com.github.javaparser.resolution.declarations.*;
import com.github.javaparser.resolution.types.*;
import com.github.javaparser.symbolsolver.logic.TypeDeclarationWithResolutionCapabilities;
import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.model.typesystem.ReferenceTypeImpl;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author Federico Tomassetti
 */
public class MethodResolutionLogic {

    private static List groupVariadicParamValues(List argumentsTypes, int startVariadic, ResolvedType variadicType) {
        List res = new ArrayList<>(argumentsTypes.subList(0, startVariadic));
        List variadicValues = argumentsTypes.subList(startVariadic, argumentsTypes.size());
        if (variadicValues.isEmpty()) {
            // TODO if there are no variadic values we should default to the bound of the formal type
            res.add(variadicType);
        } else {
            ResolvedType componentType = findCommonType(variadicValues);
            res.add(new ResolvedArrayType(componentType));
        }
        return res;
    }

    private static ResolvedType findCommonType(List variadicValues) {
        if (variadicValues.isEmpty()) {
            throw new IllegalArgumentException();
        }
        // TODO implement this decently
        return variadicValues.get(0);
    }

    public static boolean isApplicable(ResolvedMethodDeclaration method, String name, List argumentsTypes, TypeSolver typeSolver) {
        return isApplicable(method, name, argumentsTypes, typeSolver, false);
    }

    private static boolean isApplicable(ResolvedMethodDeclaration method, String name, List argumentsTypes, TypeSolver typeSolver, boolean withWildcardTolerance) {
        if (!method.getName().equals(name)) {
            return false;
        }
        if (method.hasVariadicParameter()) {
            int pos = method.getNumberOfParams() - 1;
            if (method.getNumberOfParams() == argumentsTypes.size()) {
                // check if the last value is directly assignable as an array
                ResolvedType expectedType = method.getLastParam().getType();
                ResolvedType actualType = argumentsTypes.get(pos);
                if (!expectedType.isAssignableBy(actualType)) {
                    for (ResolvedTypeParameterDeclaration tp : method.getTypeParameters()) {
                        expectedType = replaceTypeParam(expectedType, tp, typeSolver);
                    }
                    if (!expectedType.isAssignableBy(actualType)) {
                        if (actualType.isArray() && expectedType.isAssignableBy(actualType.asArrayType().getComponentType())) {
                            argumentsTypes.set(pos, actualType.asArrayType().getComponentType());
                        } else {
                            argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, method.getLastParam().getType());
                        }
                    }
                } // else it is already assignable, nothing to do
            } else {
                if (pos > argumentsTypes.size()) {
                    return false;
                }
                argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, method.getLastParam().getType());
            }
        }

        if (method.getNumberOfParams() != argumentsTypes.size()) {
            return false;
        }
        Map matchedParameters = new HashMap<>();
        boolean needForWildCardTolerance = false;
        for (int i = 0; i < method.getNumberOfParams(); i++) {
            ResolvedType expectedType = method.getParam(i).getType();
            ResolvedType actualType = argumentsTypes.get(i);
            if ((expectedType.isTypeVariable() && !(expectedType.isWildcard())) && expectedType.asTypeParameter().declaredOnMethod()) {
                matchedParameters.put(expectedType.asTypeParameter().getName(), actualType);
                continue;
            }
            boolean isAssignableWithoutSubstitution = expectedType.isAssignableBy(actualType) ||
                    (method.getParam(i).isVariadic() && new ResolvedArrayType(expectedType).isAssignableBy(actualType));
            if (!isAssignableWithoutSubstitution && expectedType.isReferenceType() && actualType.isReferenceType()) {
                isAssignableWithoutSubstitution = isAssignableMatchTypeParameters(
                        expectedType.asReferenceType(),
                        actualType.asReferenceType(),
                        matchedParameters);
            }
            if (!isAssignableWithoutSubstitution) {
                List typeParameters = method.getTypeParameters();
                typeParameters.addAll(method.declaringType().getTypeParameters());
                for (ResolvedTypeParameterDeclaration tp : typeParameters) {
                    expectedType = replaceTypeParam(expectedType, tp, typeSolver);
                }

                if (!expectedType.isAssignableBy(actualType)) {
                    if (actualType.isWildcard() && withWildcardTolerance && !expectedType.isPrimitive()) {
                        needForWildCardTolerance = true;
                        continue;
                    }
                    if (method.hasVariadicParameter() && i == method.getNumberOfParams() - 1) {
                        if (new ResolvedArrayType(expectedType).isAssignableBy(actualType)) {
                            continue;
                        }
                    }
                    return false;
                }
            }
        }
        return !withWildcardTolerance || needForWildCardTolerance;
    }

    public static boolean isAssignableMatchTypeParameters(ResolvedType expected, ResolvedType actual,
                                                          Map matchedParameters) {
        if (expected.isReferenceType() && actual.isReferenceType()) {
            return isAssignableMatchTypeParameters(expected.asReferenceType(), actual.asReferenceType(), matchedParameters);
        } else if (expected.isTypeVariable()) {
            matchedParameters.put(expected.asTypeParameter().getName(), actual);
            return true;
        } else {
            throw new UnsupportedOperationException(expected.getClass().getCanonicalName() + " " + actual.getClass().getCanonicalName());
        }
    }

    public static boolean isAssignableMatchTypeParameters(ResolvedReferenceType expected, ResolvedReferenceType actual,
                                                          Map matchedParameters) {
        if (actual.getQualifiedName().equals(expected.getQualifiedName())) {
            return isAssignableMatchTypeParametersMatchingQName(expected, actual, matchedParameters);
        } else {
            List ancestors = actual.getAllAncestors();
            for (ResolvedReferenceType ancestor : ancestors) {
                if (isAssignableMatchTypeParametersMatchingQName(expected, ancestor, matchedParameters)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean isAssignableMatchTypeParametersMatchingQName(ResolvedReferenceType expected, ResolvedReferenceType actual,
                                                                        Map matchedParameters) {

        if (!expected.getQualifiedName().equals(actual.getQualifiedName())) {
            return false;
        }
        if (expected.typeParametersValues().size() != actual.typeParametersValues().size()) {
            throw new UnsupportedOperationException();
            //return true;
        }
        for (int i = 0; i < expected.typeParametersValues().size(); i++) {
            ResolvedType expectedParam = expected.typeParametersValues().get(i);
            ResolvedType actualParam = actual.typeParametersValues().get(i);

            // In the case of nested parameterizations eg. List <-> List
            // we should peel off one layer and ensure R <-> Integer
            if (expectedParam.isReferenceType() && actualParam.isReferenceType()) {
                ResolvedReferenceType r1 = expectedParam.asReferenceType();
                ResolvedReferenceType r2 = actualParam.asReferenceType();

                return isAssignableMatchTypeParametersMatchingQName(r1, r2, matchedParameters);
            }

            if (expectedParam.isTypeVariable()) {
                String expectedParamName = expectedParam.asTypeParameter().getName();
                if (!actualParam.isTypeVariable() || !actualParam.asTypeParameter().getName().equals(expectedParamName)) {
                    return matchTypeVariable(expectedParam.asTypeVariable(), actualParam, matchedParameters);
                }
            } else if (expectedParam.isReferenceType()) {
                if (actualParam.isTypeVariable()) {
                    return matchTypeVariable(actualParam.asTypeVariable(), expectedParam, matchedParameters);
                } else if (!expectedParam.equals(actualParam)) {
                    return false;
                }
            } else if (expectedParam.isWildcard()) {
                if (expectedParam.asWildcard().isExtends()) {
                    return isAssignableMatchTypeParameters(expectedParam.asWildcard().getBoundedType(), actual, matchedParameters);
                }
                // TODO verify super bound
                return true;
            } else {
                throw new UnsupportedOperationException(expectedParam.describe());
            }
        }
        return true;
    }

    private static boolean matchTypeVariable(ResolvedTypeVariable typeVariable, ResolvedType type, Map matchedParameters) {
        String typeParameterName = typeVariable.asTypeParameter().getName();
        if (matchedParameters.containsKey(typeParameterName)) {
            ResolvedType matchedParameter = matchedParameters.get(typeParameterName);
            if (matchedParameter.isAssignableBy(type)) {
                return true;
            } else if (type.isAssignableBy(matchedParameter)) {
                // update matchedParameters to contain the more general type
                matchedParameters.put(typeParameterName, type);
                return true;
            }
            return false;
        } else {
            matchedParameters.put(typeParameterName, type);
        }
        return true;
    }

    public static ResolvedType replaceTypeParam(ResolvedType type, ResolvedTypeParameterDeclaration tp, TypeSolver typeSolver) {
        if (type.isTypeVariable() || type.isWildcard()) {
            if (type.describe().equals(tp.getName())) {
                List bounds = tp.getBounds();
                if (bounds.size() > 1) {
                    throw new UnsupportedOperationException();
                } else if (bounds.size() == 1) {
                    return bounds.get(0).getType();
                } else {
                    return new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver);
                }
            }
            return type;
        } else if (type.isPrimitive()) {
            return type;
        } else if (type.isArray()) {
            return new ResolvedArrayType(replaceTypeParam(type.asArrayType().getComponentType(), tp, typeSolver));
        } else if (type.isReferenceType()) {
            ResolvedReferenceType result = type.asReferenceType();
            result = result.transformTypeParameters(typeParam -> replaceTypeParam(typeParam, tp, typeSolver)).asReferenceType();
            return result;
        } else {
            throw new UnsupportedOperationException("Replacing " + type + ", param " + tp + " with " + type.getClass().getCanonicalName());
        }
    }

    public static boolean isApplicable(MethodUsage method, String name, List argumentsTypes, TypeSolver typeSolver) {
        if (!method.getName().equals(name)) {
            return false;
        }
        // TODO Consider varargs
        if (method.getNoParams() != argumentsTypes.size()) {
            return false;
        }
        for (int i = 0; i < method.getNoParams(); i++) {
            ResolvedType expectedType = method.getParamType(i);
            ResolvedType expectedTypeWithoutSubstitutions = expectedType;
            ResolvedType expectedTypeWithInference = method.getParamType(i);
            ResolvedType actualType = argumentsTypes.get(i);

            List typeParameters = method.getDeclaration().getTypeParameters();
            typeParameters.addAll(method.declaringType().getTypeParameters());

            if (expectedType.describe().equals(actualType.describe())) {
                return true;
            }

            Map derivedValues = new HashMap<>();
            for (int j = 0; j < method.getParamTypes().size(); j++) {
                ResolvedParameterDeclaration parameter = method.getDeclaration().getParam(i);
                ResolvedType parameterType = parameter.getType();
                if (parameter.isVariadic()) {
                    parameterType = parameterType.asArrayType().getComponentType();
                }
                inferTypes(argumentsTypes.get(j), parameterType, derivedValues);
            }

            for (Map.Entry entry : derivedValues.entrySet()) {
                ResolvedTypeParameterDeclaration tp = entry.getKey();
                expectedTypeWithInference = expectedTypeWithInference.replaceTypeVariables(tp, entry.getValue());
            }

            for (ResolvedTypeParameterDeclaration tp : typeParameters) {
                if (tp.getBounds().isEmpty()) {
                    //expectedType = expectedType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
                    expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver)));
                } else if (tp.getBounds().size() == 1) {
                    ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0);
                    if (bound.isExtends()) {
                        //expectedType = expectedType.replaceTypeVariables(tp.getName(), bound.getType());
                        expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.extendsBound(bound.getType()));
                    } else {
                        //expectedType = expectedType.replaceTypeVariables(tp.getName(), new ReferenceTypeUsageImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
                        expectedType = expectedType.replaceTypeVariables(tp, ResolvedWildcard.superBound(bound.getType()));
                    }
                } else {
                    throw new UnsupportedOperationException();
                }
            }
            ResolvedType expectedType2 = expectedTypeWithoutSubstitutions;
            for (ResolvedTypeParameterDeclaration tp : typeParameters) {
                if (tp.getBounds().isEmpty()) {
                    expectedType2 = expectedType2.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
                } else if (tp.getBounds().size() == 1) {
                    ResolvedTypeParameterDeclaration.Bound bound = tp.getBounds().get(0);
                    if (bound.isExtends()) {
                        expectedType2 = expectedType2.replaceTypeVariables(tp, bound.getType());
                    } else {
                        expectedType2 = expectedType2.replaceTypeVariables(tp, new ReferenceTypeImpl(typeSolver.solveType(Object.class.getCanonicalName()), typeSolver));
                    }
                } else {
                    throw new UnsupportedOperationException();
                }
            }
            if (!expectedType.isAssignableBy(actualType)
                    && !expectedType2.isAssignableBy(actualType)
                    && !expectedTypeWithInference.isAssignableBy(actualType)
                    && !expectedTypeWithoutSubstitutions.isAssignableBy(actualType)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Filters by given function {@param keyExtractor} using a stateful filter mechanism.
     * 
     * 
     *      persons.stream().filter(distinctByKey(Person::getName))
     * 
* * The example above would return a distinct list of persons containing only one person per name. * */ private static Predicate distinctByKey(Function keyExtractor) { Set seen = ConcurrentHashMap.newKeySet(); return t -> seen.add(keyExtractor.apply(t)); } /** * @param methods we expect the methods to be ordered such that inherited methods are later in the list */ public static SymbolReference findMostApplicable(List methods, String name, List argumentsTypes, TypeSolver typeSolver) { SymbolReference res = findMostApplicable(methods, name, argumentsTypes, typeSolver, false); if (res.isSolved()) { return res; } return findMostApplicable(methods, name, argumentsTypes, typeSolver, true); } public static SymbolReference findMostApplicable(List methods, String name, List argumentsTypes, TypeSolver typeSolver, boolean wildcardTolerance) { List methodsWithMatchingName = methods.stream() .filter(m -> m.getName().equals(name)) .collect(Collectors.toList()); List applicableMethods = methodsWithMatchingName.stream() // Filters out duplicate ResolvedMethodDeclaration by their signature. .filter(distinctByKey(ResolvedMethodDeclaration::getQualifiedSignature)) // Checks if ResolvedMethodDeclaration is applicable to argumentsTypes. .filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver, wildcardTolerance)) .collect(Collectors.toList()); if (applicableMethods.isEmpty()) { return SymbolReference.unsolved(ResolvedMethodDeclaration.class); } if (applicableMethods.size() > 1) { List nullParamIndexes = new ArrayList<>(); for (int i = 0; i < argumentsTypes.size(); i++) { if (argumentsTypes.get(i).isNull()) { nullParamIndexes.add(i); } } if (!nullParamIndexes.isEmpty()) { // remove method with array param if a non array exists and arg is null Set removeCandidates = new HashSet<>(); for (Integer nullParamIndex : nullParamIndexes) { for (ResolvedMethodDeclaration methDecl : applicableMethods) { if (methDecl.getParam(nullParamIndex).getType().isArray()) { removeCandidates.add(methDecl); } } } if (!removeCandidates.isEmpty() && removeCandidates.size() < applicableMethods.size()) { applicableMethods.removeAll(removeCandidates); } } } if (applicableMethods.size() == 1) { return SymbolReference.solved(applicableMethods.get(0)); } else { ResolvedMethodDeclaration winningCandidate = applicableMethods.get(0); ResolvedMethodDeclaration other = null; boolean possibleAmbiguity = false; for (int i = 1; i < applicableMethods.size(); i++) { other = applicableMethods.get(i); if (isMoreSpecific(winningCandidate, other, argumentsTypes)) { possibleAmbiguity = false; } else if (isMoreSpecific(other, winningCandidate, argumentsTypes)) { possibleAmbiguity = false; winningCandidate = other; } else { if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) { possibleAmbiguity = true; } else { // we expect the methods to be ordered such that inherited methods are later in the list } } } if (possibleAmbiguity) { // pick the first exact match if it exists if (!isExactMatch(winningCandidate, argumentsTypes)) { if (isExactMatch(other, argumentsTypes)) { winningCandidate = other; } else { throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method: " + winningCandidate + ", " + other); } } } return SymbolReference.solved(winningCandidate); } } protected static boolean isExactMatch(ResolvedMethodLikeDeclaration method, List argumentsTypes) { for (int i = 0; i < method.getNumberOfParams(); i++) { if (!method.getParam(i).getType().equals(argumentsTypes.get(i))) { return false; } } return true; } private static boolean isMoreSpecific(ResolvedMethodDeclaration methodA, ResolvedMethodDeclaration methodB, List argumentTypes) { boolean oneMoreSpecificFound = false; if (methodA.getNumberOfParams() < methodB.getNumberOfParams()) { return true; } if (methodA.getNumberOfParams() > methodB.getNumberOfParams()) { return false; } for (int i = 0; i < methodA.getNumberOfParams(); i++) { ResolvedType tdA = methodA.getParam(i).getType(); ResolvedType tdB = methodB.getParam(i).getType(); // B is more specific if (tdB.isAssignableBy(tdA) && !tdA.isAssignableBy(tdB)) { oneMoreSpecificFound = true; } // A is more specific if (tdA.isAssignableBy(tdB) && !tdB.isAssignableBy(tdA)) { return false; } } if (!oneMoreSpecificFound) { int lastIndex = argumentTypes.size() - 1; if (methodA.hasVariadicParameter() && !methodB.hasVariadicParameter()) { // if the last argument is an array then m1 is more specific if (argumentTypes.get(lastIndex).isArray()) { return true; } if (!argumentTypes.get(lastIndex).isArray()) { return false; } } if (!methodA.hasVariadicParameter() && methodB.hasVariadicParameter()) { // if the last argument is an array and m1 is not variadic then // it is not more specific if (argumentTypes.get(lastIndex).isArray()) { return false; } if (!argumentTypes.get(lastIndex).isArray()) { return true; } } } return oneMoreSpecificFound; } private static boolean isMoreSpecific(MethodUsage methodA, MethodUsage methodB) { boolean oneMoreSpecificFound = false; for (int i = 0; i < methodA.getNoParams(); i++) { ResolvedType tdA = methodA.getParamType(i); ResolvedType tdB = methodB.getParamType(i); boolean aIsAssignableByB = tdA.isAssignableBy(tdB); boolean bIsAssignableByA = tdB.isAssignableBy(tdA); // B is more specific if (bIsAssignableByA && !aIsAssignableByB) { oneMoreSpecificFound = true; } // A is more specific if (aIsAssignableByB && !bIsAssignableByA) { return false; } } return oneMoreSpecificFound; } public static Optional findMostApplicableUsage(List methods, String name, List argumentsTypes, TypeSolver typeSolver) { List applicableMethods = methods.stream().filter((m) -> isApplicable(m, name, argumentsTypes, typeSolver)).collect(Collectors.toList()); if (applicableMethods.isEmpty()) { return Optional.empty(); } if (applicableMethods.size() == 1) { return Optional.of(applicableMethods.get(0)); } else { MethodUsage winningCandidate = applicableMethods.get(0); for (int i = 1; i < applicableMethods.size(); i++) { MethodUsage other = applicableMethods.get(i); if (isMoreSpecific(winningCandidate, other)) { // nothing to do } else if (isMoreSpecific(other, winningCandidate)) { winningCandidate = other; } else { if (winningCandidate.declaringType().getQualifiedName().equals(other.declaringType().getQualifiedName())) { if (!areOverride(winningCandidate, other)) { throw new MethodAmbiguityException("Ambiguous method call: cannot find a most applicable method: " + winningCandidate + ", " + other + ". First declared in " + winningCandidate.declaringType().getQualifiedName()); } } else { // we expect the methods to be ordered such that inherited methods are later in the list //throw new UnsupportedOperationException(); } } } return Optional.of(winningCandidate); } } private static boolean areOverride(MethodUsage winningCandidate, MethodUsage other) { if (!winningCandidate.getName().equals(other.getName())) { return false; } if (winningCandidate.getNoParams() != other.getNoParams()) { return false; } for (int i = 0; i < winningCandidate.getNoParams(); i++) { if (!winningCandidate.getParamTypes().get(i).equals(other.getParamTypes().get(i))) { return false; } } return true; } public static SymbolReference solveMethodInType(ResolvedTypeDeclaration typeDeclaration, String name, List argumentsTypes) { return solveMethodInType(typeDeclaration, name, argumentsTypes, false); } // TODO: Replace TypeDeclaration.solveMethod public static SymbolReference solveMethodInType(ResolvedTypeDeclaration typeDeclaration, String name, List argumentsTypes, boolean staticOnly) { if (typeDeclaration instanceof TypeDeclarationWithResolutionCapabilities) { return ((TypeDeclarationWithResolutionCapabilities) typeDeclaration).solveMethod(name, argumentsTypes, staticOnly); } else { throw new UnsupportedOperationException(typeDeclaration.getClass().getCanonicalName()); } } private static void inferTypes(ResolvedType source, ResolvedType target, Map mappings) { if (source.equals(target)) { return; } if (source.isReferenceType() && target.isReferenceType()) { ResolvedReferenceType sourceRefType = source.asReferenceType(); ResolvedReferenceType targetRefType = target.asReferenceType(); if (sourceRefType.getQualifiedName().equals(targetRefType.getQualifiedName())) { if (!sourceRefType.isRawType() && !targetRefType.isRawType()) { for (int i = 0; i < sourceRefType.typeParametersValues().size(); i++) { inferTypes(sourceRefType.typeParametersValues().get(i), targetRefType.typeParametersValues().get(i), mappings); } } } return; } if (source.isReferenceType() && target.isWildcard()) { if (target.asWildcard().isBounded()) { inferTypes(source, target.asWildcard().getBoundedType(), mappings); return; } return; } if (source.isWildcard() && target.isWildcard()) { return; } if (source.isReferenceType() && target.isTypeVariable()) { mappings.put(target.asTypeParameter(), source); return; } if (source.isWildcard() && target.isReferenceType()) { if (source.asWildcard().isBounded()) { inferTypes(source.asWildcard().getBoundedType(), target, mappings); } return; } if (source.isWildcard() && target.isTypeVariable()) { mappings.put(target.asTypeParameter(), source); return; } if (source.isTypeVariable() && target.isTypeVariable()) { mappings.put(target.asTypeParameter(), source); return; } if (source.isPrimitive() || target.isPrimitive()) { return; } if (source.isNull()) { return; } } }