1 /* 2 * Copyright 2016 Federico Tomassetti 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.github.javaparser.symbolsolver.resolution; 16 17 import com.github.javaparser.resolution.MethodAmbiguityException; 18 import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; 19 import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; 20 import com.github.javaparser.resolution.types.ResolvedArrayType; 21 import com.github.javaparser.resolution.types.ResolvedType; 22 import com.github.javaparser.symbolsolver.model.resolution.SymbolReference; 23 import com.github.javaparser.symbolsolver.model.resolution.TypeSolver; 24 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.stream.Collectors; 30 31 /** 32 * @author Fred Lefvre-Laoide 33 */ 34 public class ConstructorResolutionLogic { 35 36 private static List<ResolvedType> groupVariadicParamValues(List<ResolvedType> argumentsTypes, int startVariadic, 37 ResolvedType variadicType) { 38 List<ResolvedType> res = new ArrayList<>(argumentsTypes.subList(0, startVariadic)); 39 List<ResolvedType> variadicValues = argumentsTypes.subList(startVariadic, argumentsTypes.size()); 40 if (variadicValues.isEmpty()) { 41 // TODO if there are no variadic values we should default to the bound of the formal type 42 res.add(variadicType); 43 } else { 44 ResolvedType componentType = findCommonType(variadicValues); 45 res.add(new ResolvedArrayType(componentType)); 46 } 47 return res; 48 } 49 50 private static ResolvedType findCommonType(List<ResolvedType> variadicValues) { 51 if (variadicValues.isEmpty()) { 52 throw new IllegalArgumentException(); 53 } 54 // TODO implement this decently 55 return variadicValues.get(0); 56 } 57 58 public static boolean isApplicable(ResolvedConstructorDeclaration constructor, List<ResolvedType> argumentsTypes, 59 TypeSolver typeSolver) { 60 return isApplicable(constructor, argumentsTypes, typeSolver, false); 61 } 62 63 private static boolean isApplicable(ResolvedConstructorDeclaration constructor, List<ResolvedType> argumentsTypes, 64 TypeSolver typeSolver, boolean withWildcardTolerance) { 65 if (constructor.hasVariadicParameter()) { 66 int pos = constructor.getNumberOfParams() - 1; 67 if (constructor.getNumberOfParams() == argumentsTypes.size()) { 68 // check if the last value is directly assignable as an array 69 ResolvedType expectedType = constructor.getLastParam().getType(); 70 ResolvedType actualType = argumentsTypes.get(pos); 71 if (!expectedType.isAssignableBy(actualType)) { 72 for (ResolvedTypeParameterDeclaration tp : constructor.getTypeParameters()) { 73 expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); 74 } 75 if (!expectedType.isAssignableBy(actualType)) { 76 if (actualType.isArray() 77 && expectedType.isAssignableBy(actualType.asArrayType().getComponentType())) { 78 argumentsTypes.set(pos, actualType.asArrayType().getComponentType()); 79 } else { 80 argumentsTypes = groupVariadicParamValues(argumentsTypes, pos, 81 constructor.getLastParam().getType()); 82 } 83 } 84 } // else it is already assignable, nothing to do 85 } else { 86 if (pos > argumentsTypes.size()) { 87 return false; 88 } 89 argumentsTypes = 90 groupVariadicParamValues(argumentsTypes, pos, constructor.getLastParam().getType()); 91 } 92 } 93 94 if (constructor.getNumberOfParams() != argumentsTypes.size()) { 95 return false; 96 } 97 Map<String, ResolvedType> matchedParameters = new HashMap<>(); 98 boolean needForWildCardTolerance = false; 99 for (int i = 0; i < constructor.getNumberOfParams(); i++) { 100 ResolvedType expectedType = constructor.getParam(i).getType(); 101 ResolvedType actualType = argumentsTypes.get(i); 102 if ((expectedType.isTypeVariable() && !(expectedType.isWildcard())) 103 && expectedType.asTypeParameter().declaredOnMethod()) { 104 matchedParameters.put(expectedType.asTypeParameter().getName(), actualType); 105 continue; 106 } 107 boolean isAssignableWithoutSubstitution = 108 expectedType.isAssignableBy(actualType) || (constructor.getParam(i).isVariadic() 109 && new ResolvedArrayType(expectedType).isAssignableBy(actualType)); 110 if (!isAssignableWithoutSubstitution && expectedType.isReferenceType() 111 && actualType.isReferenceType()) { 112 isAssignableWithoutSubstitution = MethodResolutionLogic.isAssignableMatchTypeParameters( 113 expectedType.asReferenceType(), actualType.asReferenceType(), matchedParameters); 114 } 115 if (!isAssignableWithoutSubstitution) { 116 List<ResolvedTypeParameterDeclaration> typeParameters = constructor.getTypeParameters(); 117 typeParameters.addAll(constructor.declaringType().getTypeParameters()); 118 for (ResolvedTypeParameterDeclaration tp : typeParameters) { 119 expectedType = MethodResolutionLogic.replaceTypeParam(expectedType, tp, typeSolver); 120 } 121 122 if (!expectedType.isAssignableBy(actualType)) { 123 if (actualType.isWildcard() && withWildcardTolerance && !expectedType.isPrimitive()) { 124 needForWildCardTolerance = true; 125 continue; 126 } 127 if (constructor.hasVariadicParameter() && i == constructor.getNumberOfParams() - 1) { 128 if (new ResolvedArrayType(expectedType).isAssignableBy(actualType)) { 129 continue; 130 } 131 } 132 return false; 133 } 134 } 135 } 136 return !withWildcardTolerance || needForWildCardTolerance; 137 } 138 139 /** 140 * @param constructors we expect the methods to be ordered such that inherited methods are later in the list 141 * @param argumentsTypes 142 * @param typeSolver 143 * @return 144 */ 145 public static SymbolReference<ResolvedConstructorDeclaration> findMostApplicable( 146 List<ResolvedConstructorDeclaration> constructors, List<ResolvedType> argumentsTypes, TypeSolver typeSolver) { 147 SymbolReference<ResolvedConstructorDeclaration> res = 148 findMostApplicable(constructors, argumentsTypes, typeSolver, false); 149 if (res.isSolved()) { 150 return res; 151 } 152 return findMostApplicable(constructors, argumentsTypes, typeSolver, true); 153 } 154 155 public static SymbolReference<ResolvedConstructorDeclaration> findMostApplicable( 156 List<ResolvedConstructorDeclaration> constructors, List<ResolvedType> argumentsTypes, TypeSolver typeSolver, boolean wildcardTolerance) { 157 List<ResolvedConstructorDeclaration> applicableConstructors = constructors.stream().filter((m) -> isApplicable(m, argumentsTypes, typeSolver, wildcardTolerance)).collect(Collectors.toList()); 158 if (applicableConstructors.isEmpty()) { 159 return SymbolReference.unsolved(ResolvedConstructorDeclaration.class); 160 } 161 if (applicableConstructors.size() == 1) { 162 return SymbolReference.solved(applicableConstructors.get(0)); 163 } else { 164 ResolvedConstructorDeclaration winningCandidate = applicableConstructors.get(0); 165 ResolvedConstructorDeclaration other = null; 166 boolean possibleAmbiguity = false; 167 for (int i = 1; i < applicableConstructors.size(); i++) { 168 other = applicableConstructors.get(i); 169 if (isMoreSpecific(winningCandidate, other, typeSolver)) { 170 possibleAmbiguity = false; 171 } else if (isMoreSpecific(other, winningCandidate, typeSolver)) { 172 possibleAmbiguity = false; 173 winningCandidate = other; 174 } else { 175 if (winningCandidate.declaringType().getQualifiedName() 176 .equals(other.declaringType().getQualifiedName())) { 177 possibleAmbiguity = true; 178 } else { 179 // we expect the methods to be ordered such that inherited methods are later in the list 180 } 181 } 182 } 183 if (possibleAmbiguity) { 184 // pick the first exact match if it exists 185 if (!MethodResolutionLogic.isExactMatch(winningCandidate, argumentsTypes)) { 186 if (MethodResolutionLogic.isExactMatch(other, argumentsTypes)) { 187 winningCandidate = other; 188 } else { 189 throw new MethodAmbiguityException("Ambiguous constructor call: cannot find a most applicable constructor: " + winningCandidate + ", " + other); 190 } 191 } 192 } 193 return SymbolReference.solved(winningCandidate); 194 } 195 } 196 197 private static boolean isMoreSpecific(ResolvedConstructorDeclaration constructorA, 198 ResolvedConstructorDeclaration constructorB, TypeSolver typeSolver) { 199 boolean oneMoreSpecificFound = false; 200 if (constructorA.getNumberOfParams() < constructorB.getNumberOfParams()) { 201 return true; 202 } 203 if (constructorA.getNumberOfParams() > constructorB.getNumberOfParams()) { 204 return false; 205 } 206 for (int i = 0; i < constructorA.getNumberOfParams(); i++) { 207 ResolvedType tdA = constructorA.getParam(i).getType(); 208 ResolvedType tdB = constructorB.getParam(i).getType(); 209 // B is more specific 210 if (tdB.isAssignableBy(tdA) && !tdA.isAssignableBy(tdB)) { 211 oneMoreSpecificFound = true; 212 } 213 // A is more specific 214 if (tdA.isAssignableBy(tdB) && !tdB.isAssignableBy(tdA)) { 215 return false; 216 } 217 // if it matches a variadic and a not variadic I pick the not variadic 218 // FIXME 219 if (i == (constructorA.getNumberOfParams() - 1) && tdA.arrayLevel() > tdB.arrayLevel()) { 220 return true; 221 } 222 } 223 return oneMoreSpecificFound; 224 } 225 226 } 227