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"); 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