diff options
Diffstat (limited to 'javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java')
-rw-r--r-- | javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java new file mode 100644 index 000000000..dfd50ed48 --- /dev/null +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/resolution/ConstructorResolutionLogic.java @@ -0,0 +1,226 @@ +/* + * 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.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; +import com.github.javaparser.resolution.types.ResolvedArrayType; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; +import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Fred Lefévère-Laoide + */ +public class ConstructorResolutionLogic { + + private static List<ResolvedType> groupVariadicParamValues(List<ResolvedType> argumentsTypes, int startVariadic, + ResolvedType variadicType) { + List<ResolvedType> res = new ArrayList<>(argumentsTypes.subList(0, startVariadic)); + List<ResolvedType> 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<ResolvedType> variadicValues) { + if (variadicValues.isEmpty()) { + throw new IllegalArgumentException(); + } + // TODO implement this decently + return variadicValues.get(0); + } + + public static boolean isApplicable(ResolvedConstructorDeclaration constructor, List<ResolvedType> argumentsTypes, + TypeSolver typeSolver) { + return isApplicable(constructor, argumentsTypes, typeSolver, false); + } + + private static boolean isApplicable(ResolvedConstructorDeclaration constructor, List<ResolvedType> argumentsTypes, + TypeSolver typeSolver, boolean withWildcardTolerance) { + if (constructor.hasVariadicParameter()) { + int pos = constructor.getNumberOfParams() - 1; + if (constructor.getNumberOfParams() == argumentsTypes.size()) { + // check if the last value is directly assignable as an array + ResolvedType expectedType = constructor.getLastParam().getType(); + ResolvedType actualType = argumentsTypes.get(pos); + if (!expectedType.isAssignableBy(actualType)) { + for (ResolvedTypeParameterDeclaration tp : constructor.getTypeParameters()) { + expectedType = MethodResolutionLogic.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, + constructor.getLastParam().getType()); + } + } + } // else it is already assignable, nothing to do + } else { + if (pos > argumentsTypes.size()) { + return false; + } + argumentsTypes = + groupVariadicParamValues(argumentsTypes, pos, constructor.getLastParam().getType()); + } + } + + if (constructor.getNumberOfParams() != argumentsTypes.size()) { + return false; + } + Map<String, ResolvedType> matchedParameters = new HashMap<>(); + boolean needForWildCardTolerance = false; + for (int i = 0; i < constructor.getNumberOfParams(); i++) { + ResolvedType expectedType = constructor.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) || (constructor.getParam(i).isVariadic() + && new ResolvedArrayType(expectedType).isAssignableBy(actualType)); + if (!isAssignableWithoutSubstitution && expectedType.isReferenceType() + && actualType.isReferenceType()) { + isAssignableWithoutSubstitution = MethodResolutionLogic.isAssignableMatchTypeParameters( + expectedType.asReferenceType(), actualType.asReferenceType(), matchedParameters); + } + if (!isAssignableWithoutSubstitution) { + List<ResolvedTypeParameterDeclaration> typeParameters = constructor.getTypeParameters(); + typeParameters.addAll(constructor.declaringType().getTypeParameters()); + for (ResolvedTypeParameterDeclaration tp : typeParameters) { + expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); + } + + if (!expectedType.isAssignableBy(actualType)) { + if (actualType.isWildcard() && withWildcardTolerance && !expectedType.isPrimitive()) { + needForWildCardTolerance = true; + continue; + } + if (constructor.hasVariadicParameter() && i == constructor.getNumberOfParams() - 1) { + if (new ResolvedArrayType(expectedType).isAssignableBy(actualType)) { + continue; + } + } + return false; + } + } + } + return !withWildcardTolerance || needForWildCardTolerance; + } + + /** + * @param constructors we expect the methods to be ordered such that inherited methods are later in the list + * @param argumentsTypes + * @param typeSolver + * @return + */ + public static SymbolReference<ResolvedConstructorDeclaration> findMostApplicable( + List<ResolvedConstructorDeclaration> constructors, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) { + SymbolReference<ResolvedConstructorDeclaration> res = + findMostApplicable(constructors, argumentsTypes, typeSolver, false); + if (res.isSolved()) { + return res; + } + return findMostApplicable(constructors, argumentsTypes, typeSolver, true); + } + + public static SymbolReference<ResolvedConstructorDeclaration> findMostApplicable( + List<ResolvedConstructorDeclaration> constructors, List<ResolvedType> argumentsTypes, TypeSolver typeSolver, boolean wildcardTolerance) { + List<ResolvedConstructorDeclaration> applicableConstructors = constructors.stream().filter((m) -> isApplicable(m, argumentsTypes, typeSolver, wildcardTolerance)).collect(Collectors.toList()); + if (applicableConstructors.isEmpty()) { + return SymbolReference.unsolved(ResolvedConstructorDeclaration.class); + } + if (applicableConstructors.size() == 1) { + return SymbolReference.solved(applicableConstructors.get(0)); + } else { + ResolvedConstructorDeclaration winningCandidate = applicableConstructors.get(0); + ResolvedConstructorDeclaration other = null; + boolean possibleAmbiguity = false; + for (int i = 1; i < applicableConstructors.size(); i++) { + other = applicableConstructors.get(i); + if (isMoreSpecific(winningCandidate, other, typeSolver)) { + possibleAmbiguity = false; + } else if (isMoreSpecific(other, winningCandidate, typeSolver)) { + 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 (!MethodResolutionLogic.isExactMatch(winningCandidate, argumentsTypes)) { + if (MethodResolutionLogic.isExactMatch(other, argumentsTypes)) { + winningCandidate = other; + } else { + throw new MethodAmbiguityException("Ambiguous constructor call: cannot find a most applicable constructor: " + winningCandidate + ", " + other); + } + } + } + return SymbolReference.solved(winningCandidate); + } + } + + private static boolean isMoreSpecific(ResolvedConstructorDeclaration constructorA, + ResolvedConstructorDeclaration constructorB, TypeSolver typeSolver) { + boolean oneMoreSpecificFound = false; + if (constructorA.getNumberOfParams() < constructorB.getNumberOfParams()) { + return true; + } + if (constructorA.getNumberOfParams() > constructorB.getNumberOfParams()) { + return false; + } + for (int i = 0; i < constructorA.getNumberOfParams(); i++) { + ResolvedType tdA = constructorA.getParam(i).getType(); + ResolvedType tdB = constructorB.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 it matches a variadic and a not variadic I pick the not variadic + // FIXME + if (i == (constructorA.getNumberOfParams() - 1) && tdA.arrayLevel() > tdB.arrayLevel()) { + return true; + } + } + return oneMoreSpecificFound; + } + +} |