| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.plugins.groovy.lang.resolve.processors; |
| |
| import com.intellij.psi.*; |
| import com.intellij.psi.scope.JavaScopeProcessorEvent; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.hash.HashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; |
| import org.jetbrains.plugins.groovy.lang.psi.api.SpreadState; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| import org.jetbrains.plugins.groovy.lang.resolve.GrMethodComparator; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * @author ven |
| */ |
| public class MethodResolverProcessor extends ResolverProcessor implements GrMethodComparator.Context { |
| private final PsiType myThisType; |
| |
| @Nullable |
| private final PsiType[] myArgumentTypes; |
| |
| private final boolean myAllVariants; |
| |
| private Set<GroovyResolveResult> myInapplicableCandidates = null; |
| |
| private final boolean myIsConstructor; |
| |
| private boolean myStopExecuting = false; |
| |
| private final boolean myByShape; |
| |
| private final SubstitutorComputer mySubstitutorComputer; |
| |
| public MethodResolverProcessor(@Nullable String name, |
| @NotNull PsiElement place, |
| boolean isConstructor, |
| @Nullable PsiType thisType, |
| @Nullable PsiType[] argumentTypes, |
| @Nullable PsiType[] typeArguments) { |
| this(name, place, isConstructor, thisType, argumentTypes, typeArguments, false, false); |
| } |
| |
| public MethodResolverProcessor(@Nullable String name, |
| @NotNull PsiElement place, |
| boolean isConstructor, |
| @Nullable PsiType thisType, |
| @Nullable PsiType[] argumentTypes, |
| @Nullable PsiType[] typeArguments, |
| boolean allVariants, |
| final boolean byShape) { |
| super(name, RESOLVE_KINDS_METHOD_PROPERTY, place, PsiType.EMPTY_ARRAY); |
| myIsConstructor = isConstructor; |
| myThisType = thisType; |
| myArgumentTypes = argumentTypes; |
| myAllVariants = allVariants; |
| myByShape = byShape; |
| |
| mySubstitutorComputer = new SubstitutorComputer(myThisType, myArgumentTypes, typeArguments, myPlace, myPlace.getParent()); |
| } |
| |
| |
| @Override |
| public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { |
| if (myStopExecuting) { |
| return false; |
| } |
| if (element instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod) element; |
| |
| if (method.isConstructor() != myIsConstructor) return true; |
| |
| PsiSubstitutor substitutor = inferSubstitutor(method, state); |
| |
| PsiElement resolveContext = state.get(RESOLVE_CONTEXT); |
| final SpreadState spreadState = state.get(SpreadState.SPREAD_STATE); |
| |
| boolean isAccessible = isAccessible(method); |
| boolean isStaticsOK = isStaticsOK(method, resolveContext, false); |
| boolean isApplicable = PsiUtil.isApplicable(myArgumentTypes, method, substitutor, myPlace, myByShape); |
| boolean isValidResult = isStaticsOK && isAccessible && isApplicable; |
| |
| GroovyResolveResultImpl candidate = new GroovyResolveResultImpl(method, resolveContext, spreadState, substitutor, isAccessible, isStaticsOK, false, isValidResult); |
| |
| if (!myAllVariants && isValidResult) { |
| addCandidate(candidate); |
| } |
| else { |
| addInapplicableCandidate(candidate); |
| } |
| } |
| |
| return true; |
| } |
| |
| protected boolean addInapplicableCandidate(@NotNull GroovyResolveResult candidate) { |
| if (myInapplicableCandidates == null) { |
| myInapplicableCandidates = ContainerUtil.newLinkedHashSet(); |
| } |
| return myInapplicableCandidates.add(candidate); |
| } |
| |
| @NotNull |
| private PsiSubstitutor inferSubstitutor(@NotNull PsiMethod method, @NotNull ResolveState state) { |
| PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY); |
| if (substitutor == null) substitutor = PsiSubstitutor.EMPTY; |
| |
| return myByShape ? substitutor |
| : mySubstitutorComputer.obtainSubstitutor(substitutor, method, state); |
| } |
| |
| @Override |
| @NotNull |
| public GroovyResolveResult[] getCandidates() { |
| if (!myAllVariants && hasApplicableCandidates()) { |
| return filterCandidates(); |
| } |
| if (myInapplicableCandidates != null && !myInapplicableCandidates.isEmpty()) { |
| Set<GroovyResolveResult> resultSet = myAllVariants ? myInapplicableCandidates |
| : filterCorrectParameterCount(myInapplicableCandidates); |
| return ResolveUtil.filterSameSignatureCandidates(resultSet); |
| } |
| return GroovyResolveResult.EMPTY_ARRAY; |
| } |
| |
| private Set<GroovyResolveResult> filterCorrectParameterCount(Set<GroovyResolveResult> candidates) { |
| if (myArgumentTypes == null) return candidates; |
| Set<GroovyResolveResult> result = new HashSet<GroovyResolveResult>(); |
| for (GroovyResolveResult candidate : candidates) { |
| final PsiElement element = candidate.getElement(); |
| if (element instanceof PsiMethod && ((PsiMethod)element).getParameterList().getParametersCount() == myArgumentTypes.length) { |
| result.add(candidate); |
| } |
| } |
| if (!result.isEmpty()) return result; |
| return candidates; |
| } |
| |
| private GroovyResolveResult[] filterCandidates() { |
| List<GroovyResolveResult> array = getCandidatesInternal(); |
| if (array.size() == 1) return array.toArray(new GroovyResolveResult[array.size()]); |
| |
| List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>(); |
| |
| Iterator<GroovyResolveResult> itr = array.iterator(); |
| |
| result.add(itr.next()); |
| |
| Outer: |
| while (itr.hasNext()) { |
| GroovyResolveResult resolveResult = itr.next(); |
| PsiElement currentElement = resolveResult.getElement(); |
| if (currentElement instanceof PsiMethod) { |
| PsiMethod currentMethod = (PsiMethod) currentElement; |
| for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) { |
| final GroovyResolveResult otherResolveResult = iterator.next(); |
| PsiElement other = otherResolveResult.getElement(); |
| if (other instanceof PsiMethod) { |
| PsiMethod otherMethod = (PsiMethod) other; |
| int res = compareMethods(currentMethod, resolveResult.getSubstitutor(), resolveResult.getCurrentFileResolveContext(), |
| otherMethod, otherResolveResult.getSubstitutor(), otherResolveResult.getCurrentFileResolveContext()); |
| if (res > 0) { |
| continue Outer; |
| } |
| else if (res < 0) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| |
| result.add(resolveResult); |
| } |
| |
| return result.toArray(new GroovyResolveResult[result.size()]); |
| } |
| |
| /** |
| * |
| * @return 1 if second is more preferable |
| * 0 if methods are equal |
| * -1 if first is more preferable |
| */ |
| private int compareMethods(@NotNull PsiMethod method1, |
| @NotNull PsiSubstitutor substitutor1, |
| @Nullable PsiElement resolveContext1, |
| @NotNull PsiMethod method2, |
| @NotNull PsiSubstitutor substitutor2, |
| @Nullable PsiElement resolveContext2) { |
| if (!method1.getName().equals(method2.getName())) return 0; |
| |
| if (secondMethodIsPreferable(method1, substitutor1, resolveContext1, method2, substitutor2, resolveContext2)) { |
| if (secondMethodIsPreferable(method2, substitutor2, resolveContext2, method1, substitutor1, resolveContext1)) { |
| if (method2 instanceof GrGdkMethod && !(method1 instanceof GrGdkMethod)) { |
| return -1; |
| } |
| } |
| return 1; |
| } |
| if (secondMethodIsPreferable(method2, substitutor2, resolveContext2, method1, substitutor1, resolveContext1)) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| //method1 has more general parameter types thn method2 |
| private boolean secondMethodIsPreferable(@NotNull PsiMethod method1, |
| @NotNull PsiSubstitutor substitutor1, |
| @Nullable PsiElement resolveContext1, |
| @NotNull PsiMethod method2, |
| @NotNull PsiSubstitutor substitutor2, |
| @Nullable PsiElement resolveContext2) { |
| if (!method1.getName().equals(method2.getName())) return false; |
| |
| final Boolean custom = GrMethodComparator.checkDominated(method1, substitutor1, method2, substitutor2, this); |
| if (custom != null) return custom; |
| |
| PsiType[] argTypes = myArgumentTypes; |
| if (method1 instanceof GrGdkMethod && method2 instanceof GrGdkMethod) { |
| method1 = ((GrGdkMethod)method1).getStaticMethod(); |
| method2 = ((GrGdkMethod)method2).getStaticMethod(); |
| if (myArgumentTypes != null) { |
| argTypes = PsiType.createArray(argTypes.length + 1); |
| System.arraycopy(myArgumentTypes, 0, argTypes, 1, myArgumentTypes.length); |
| argTypes[0] = myThisType; |
| } |
| } |
| |
| if (myIsConstructor && argTypes != null && argTypes.length == 1) { |
| if (method1.getParameterList().getParametersCount() == 0) return true; |
| if (method2.getParameterList().getParametersCount() == 0) return false; |
| } |
| |
| PsiParameter[] params1 = method1.getParameterList().getParameters(); |
| PsiParameter[] params2 = method2.getParameterList().getParameters(); |
| if (argTypes == null && params1.length != params2.length) return false; |
| |
| if (params1.length < params2.length) { |
| if (params1.length == 0) return false; |
| final PsiType lastType = params1[params1.length - 1].getType(); //varargs applicability |
| return lastType instanceof PsiArrayType; |
| } |
| |
| for (int i = 0; i < params2.length; i++) { |
| final PsiType ptype1 = params1[i].getType(); |
| final PsiType ptype2 = params2[i].getType(); |
| PsiType type1 = substitutor1.substitute(ptype1); |
| PsiType type2 = substitutor2.substitute(ptype2); |
| |
| if (argTypes != null && argTypes.length > i) { |
| PsiType argType = argTypes[i]; |
| if (argType != null) { |
| final boolean converts1 = TypesUtil.isAssignableWithoutConversions(TypeConversionUtil.erasure(type1), argType, myPlace); |
| final boolean converts2 = TypesUtil.isAssignableWithoutConversions(TypeConversionUtil.erasure(type2), argType, myPlace); |
| if (converts1 != converts2) { |
| return converts2; |
| } |
| |
| // see groovy.lang.GroovyCallable |
| if (TypesUtil.resolvesTo(type1, CommonClassNames.JAVA_UTIL_CONCURRENT_CALLABLE) && |
| TypesUtil.resolvesTo(type2, CommonClassNames.JAVA_LANG_RUNNABLE)) { |
| if (InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_GROOVY_CALLABLE)) return true; |
| } |
| |
| } |
| } |
| |
| if (!typesAgree(TypeConversionUtil.erasure(ptype1), TypeConversionUtil.erasure(ptype2))) return false; |
| |
| if (resolveContext1 != null && resolveContext2 == null) { |
| return !(TypesUtil.resolvesTo(type1, CommonClassNames.JAVA_LANG_OBJECT) && |
| TypesUtil.resolvesTo(type2, CommonClassNames.JAVA_LANG_OBJECT)); |
| } |
| |
| if (resolveContext1 == null && resolveContext2 != null) { |
| return true; |
| } |
| } |
| |
| if (!(method1 instanceof SyntheticElement) && !(method2 instanceof SyntheticElement)) { |
| final PsiType returnType1 = substitutor1.substitute(method1.getReturnType()); |
| final PsiType returnType2 = substitutor2.substitute(method2.getReturnType()); |
| |
| if (!TypesUtil.isAssignableWithoutConversions(returnType1, returnType2, myPlace) && |
| TypesUtil.isAssignableWithoutConversions(returnType2, returnType1, myPlace)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean typesAgree(@NotNull PsiType type1, @NotNull PsiType type2) { |
| if (argumentsSupplied() && type1 instanceof PsiArrayType && !(type2 instanceof PsiArrayType)) { |
| type1 = ((PsiArrayType) type1).getComponentType(); |
| } |
| return argumentsSupplied() ? //resolve, otherwise same_name_variants |
| TypesUtil.isAssignableWithoutConversions(type1, type2, myPlace) : |
| type1.equals(type2); |
| } |
| |
| private boolean argumentsSupplied() { |
| return myArgumentTypes != null; |
| } |
| |
| |
| @Override |
| public boolean hasCandidates() { |
| return hasApplicableCandidates() || myInapplicableCandidates != null && !myInapplicableCandidates.isEmpty(); |
| } |
| |
| public boolean hasApplicableCandidates() { |
| return super.hasCandidates(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiType[] getArgumentTypes() { |
| return myArgumentTypes; |
| } |
| |
| @Nullable |
| @Override |
| public PsiType[] getTypeArguments() { |
| return mySubstitutorComputer.getTypeArguments(); |
| } |
| |
| @Override |
| public void handleEvent(@NotNull Event event, Object associated) { |
| super.handleEvent(event, associated); |
| if (JavaScopeProcessorEvent.CHANGE_LEVEL == event && hasApplicableCandidates()) { |
| myStopExecuting = true; |
| } |
| } |
| |
| @Nullable |
| @Override |
| public PsiType getThisType() { |
| return myThisType; |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getPlace() { |
| return myPlace; |
| } |
| |
| } |