IDEA-22821
[fedora-idea.git] / java / java-impl / src / com / intellij / psi / scope / conflictResolvers / JavaMethodsConflictResolver.java
blob1ca3b4b5de976b788dd4691192b83b847f0c9504
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.psi.scope.conflictResolvers;
18 import com.intellij.pom.java.LanguageLevel;
19 import com.intellij.psi.*;
20 import com.intellij.psi.infos.CandidateInfo;
21 import com.intellij.psi.infos.MethodCandidateInfo;
22 import com.intellij.psi.scope.PsiConflictResolver;
23 import com.intellij.psi.util.*;
24 import com.intellij.util.Function;
25 import com.intellij.util.containers.ContainerUtil;
26 import gnu.trove.THashSet;
28 import java.util.*;
30 /**
31 * Created by IntelliJ IDEA.
32 * User: ik
33 * Date: 10.06.2003
34 * Time: 19:41:51
35 * To change this template use Options | File Templates.
37 public class JavaMethodsConflictResolver implements PsiConflictResolver{
38 private final PsiElement myArgumentsList;
39 private final PsiType[] myActualParameterTypes;
40 private static final Function<PsiExpression,PsiType> EXPRESSION_TO_TYPE = new Function<PsiExpression, PsiType>() {
41 public PsiType fun(final PsiExpression expression) {
42 return expression.getType();
46 public JavaMethodsConflictResolver(PsiExpressionList list) {
47 myArgumentsList = list;
48 myActualParameterTypes = ContainerUtil.map2Array(list.getExpressions(), PsiType.class, EXPRESSION_TO_TYPE);
51 public JavaMethodsConflictResolver(final PsiElement argumentsList, final PsiType[] actualParameterTypes) {
52 myArgumentsList = argumentsList;
53 myActualParameterTypes = actualParameterTypes;
56 public CandidateInfo resolveConflict(List<CandidateInfo> conflicts){
57 if (conflicts.isEmpty()) return null;
58 if (conflicts.size() == 1) return conflicts.get(0);
59 checkSameSignatures(conflicts);
61 if (conflicts.size() == 1) return conflicts.get(0);
62 checkAccessLevels(conflicts);
64 if (conflicts.size() == 1) return conflicts.get(0);
66 checkParametersNumber(conflicts, myActualParameterTypes.length);
67 if (conflicts.size() == 1) return conflicts.get(0);
69 final int applicabilityLevel = checkApplicability(conflicts);
70 if (conflicts.size() == 1) return conflicts.get(0);
71 checkSpecifics(conflicts, applicabilityLevel);
73 if (conflicts.size() == 1) return conflicts.get(0);
75 THashSet<CandidateInfo> uniques = new THashSet<CandidateInfo>(conflicts);
76 if (uniques.size() == 1) return uniques.iterator().next();
77 return null;
80 private void checkSpecifics(List<CandidateInfo> conflicts, int applicabilityLevel) {
81 final boolean applicable = applicabilityLevel > MethodCandidateInfo.ApplicabilityLevel.NOT_APPLICABLE;
83 int conflictsCount = conflicts.size();
84 // Specifics
85 if (applicable) {
86 final CandidateInfo[] newConflictsArray = conflicts.toArray(new CandidateInfo[conflicts.size()]);
87 for (int i = 1; i < conflictsCount; i++) {
88 final CandidateInfo method = newConflictsArray[i];
89 for (int j = 0; j < i; j++) {
90 final CandidateInfo conflict = newConflictsArray[j];
91 assert conflict != method;
92 switch (isMoreSpecific(method, conflict, applicabilityLevel)) {
93 case FIRST:
94 conflicts.remove(conflict);
95 break;
96 case SECOND:
97 conflicts.remove(method);
98 break;
99 case NEITHER:
100 break;
107 private static void checkAccessLevels(List<CandidateInfo> conflicts) {
108 int conflictsCount = conflicts.size();
110 int maxCheckLevel = -1;
111 int[] checkLevels = new int[conflictsCount];
112 int index = 0;
113 for (final CandidateInfo conflict : conflicts) {
114 final MethodCandidateInfo method = (MethodCandidateInfo)conflict;
115 final int level = getCheckLevel(method);
116 checkLevels[index++] = level;
117 maxCheckLevel = Math.max(maxCheckLevel, level);
120 for (int i = conflictsCount - 1; i >= 0; i--) {
121 // check for level
122 if (checkLevels[i] < maxCheckLevel) {
123 conflicts.remove(i);
128 private static void checkSameSignatures(final List<CandidateInfo> conflicts) {
129 // candidates should go in order of class hierarchy traversal
130 // in order for this to work
131 Map<MethodSignature, CandidateInfo> signatures = new HashMap<MethodSignature, CandidateInfo>();
132 for (int i=0; i<conflicts.size();i++) {
133 CandidateInfo info = conflicts.get(i);
134 PsiMethod method = (PsiMethod)info.getElement();
135 assert method != null;
136 PsiClass class1 = method.getContainingClass();
137 PsiSubstitutor infoSubstitutor = info.getSubstitutor();
138 MethodSignature signature = method.getSignature(infoSubstitutor);
139 CandidateInfo existing = signatures.get(signature);
141 if (existing == null) {
142 signatures.put(signature, info);
143 continue;
145 PsiMethod existingMethod = (PsiMethod)existing.getElement();
146 assert existingMethod != null;
147 PsiClass existingClass = existingMethod.getContainingClass();
148 if (class1.isInterface() && "java.lang.Object".equals(existingClass.getQualifiedName())) { //prefer interface methods to methods from Object
149 signatures.put(signature, info);
150 continue;
152 if (method == existingMethod) {
153 PsiElement scope1 = info.getCurrentFileResolveScope();
154 PsiElement scope2 = existing.getCurrentFileResolveScope();
155 if (scope1 instanceof PsiClass &&
156 scope2 instanceof PsiClass &&
157 PsiTreeUtil.isAncestor(scope1, scope2, true) &&
158 !existing.isAccessible()) { //prefer methods from outer class to inaccessible base class methods
159 signatures.put(signature, info);
160 continue;
164 // filter out methods with incorrect inferred bounds (for unrelated methods only)
165 boolean existingTypeParamAgree = areTypeParametersAgree(existing);
166 boolean infoTypeParamAgree = areTypeParametersAgree(info);
167 if (existingTypeParamAgree && !infoTypeParamAgree && !PsiSuperMethodUtil.isSuperMethod(method, existingMethod)) {
168 conflicts.remove(i);
169 i--;
170 continue;
172 else if (!existingTypeParamAgree && infoTypeParamAgree && !PsiSuperMethodUtil.isSuperMethod(existingMethod, method)) {
173 signatures.put(signature, info);
174 int index = conflicts.indexOf(existing);
175 conflicts.remove(index);
176 i--;
177 continue;
180 PsiType returnType1 = method.getReturnType();
181 PsiType returnType2 = existingMethod.getReturnType();
182 if (returnType1 != null && returnType2 != null) {
183 returnType1 = infoSubstitutor.substitute(returnType1);
184 returnType2 = existing.getSubstitutor().substitute(returnType2);
185 if (returnType1.isAssignableFrom(returnType2) &&
186 (InheritanceUtil.isInheritorOrSelf(class1, existingClass, true) ||
187 InheritanceUtil.isInheritorOrSelf(existingClass, class1, true))) {
188 conflicts.remove(i);
189 i--;
195 private static boolean areTypeParametersAgree(CandidateInfo info) {
196 return ((MethodCandidateInfo)info).isApplicable();
199 private static void checkParametersNumber(final List<CandidateInfo> conflicts, final int argumentsCount) {
200 boolean parametersNumberMatch = false;
201 for (CandidateInfo info : conflicts) {
202 if (info instanceof MethodCandidateInfo) {
203 final PsiMethod method = ((MethodCandidateInfo)info).getElement();
204 if (method.isVarArgs()) return;
205 if (method.getParameterList().getParametersCount() == argumentsCount) {
206 parametersNumberMatch = true;
211 if (parametersNumberMatch) {
212 for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) {
213 CandidateInfo info = iterator.next();
214 if (info instanceof MethodCandidateInfo) {
215 final PsiMethod method = ((MethodCandidateInfo)info).getElement();
216 if (method.getParameterList().getParametersCount() != argumentsCount) {
217 iterator.remove();
224 private static int checkApplicability(List<CandidateInfo> conflicts) {
225 int maxApplicabilityLevel = 0;
226 boolean toFilter = false;
227 for (CandidateInfo conflict : conflicts) {
228 final int level = ((MethodCandidateInfo)conflict).getApplicabilityLevel();
229 if (maxApplicabilityLevel > 0 && maxApplicabilityLevel != level) {
230 toFilter = true;
232 if (level > maxApplicabilityLevel) {
233 maxApplicabilityLevel = level;
237 if (toFilter) {
238 for (Iterator<CandidateInfo> iterator = conflicts.iterator(); iterator.hasNext();) {
239 CandidateInfo info = iterator.next();
240 final int level = ((MethodCandidateInfo)info).getApplicabilityLevel(); //cached
241 if (level < maxApplicabilityLevel) {
242 iterator.remove();
247 return maxApplicabilityLevel;
250 private static int getCheckLevel(MethodCandidateInfo method){
251 boolean visible = method.isAccessible();// && !method.myStaticProblem;
252 boolean available = method.isStaticsScopeCorrect();
253 return (visible ? 1 : 0) << 2 |
254 (available ? 1 : 0) << 1 |
255 (method.getCurrentFileResolveScope() instanceof PsiImportStaticStatement ? 0 : 1);
258 private enum Specifics {
259 FIRST,
260 SECOND,
261 NEITHER
264 private static Specifics checkSubtyping(PsiType type1, PsiType type2) {
265 boolean noBoxing = type1 instanceof PsiPrimitiveType == type2 instanceof PsiPrimitiveType;
266 final boolean assignable2From1 = noBoxing && TypeConversionUtil.isAssignable(type2, type1, false);
267 final boolean assignable1From2 = noBoxing && TypeConversionUtil.isAssignable(type1, type2, false);
268 if (assignable1From2 || assignable2From1) {
269 if (assignable1From2 && assignable2From1) {
270 return null;
273 return assignable1From2 ? Specifics.SECOND : Specifics.FIRST;
276 return Specifics.NEITHER;
279 private boolean isBoxingHappened(PsiType argType, PsiType parameterType) {
280 if (argType == null) return parameterType instanceof PsiPrimitiveType;
281 final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(myArgumentsList);
282 if (parameterType instanceof PsiClassType) {
283 parameterType = ((PsiClassType)parameterType).setLanguageLevel(languageLevel);
286 return TypeConversionUtil.boxingConversionApplicable(parameterType, argType);
289 private Specifics isMoreSpecific(final CandidateInfo info1, final CandidateInfo info2, final int applicabilityLevel) {
290 PsiMethod method1 = (PsiMethod)info1.getElement();
291 PsiMethod method2 = (PsiMethod)info2.getElement();
292 final PsiClass class1 = method1.getContainingClass();
293 final PsiClass class2 = method2.getContainingClass();
295 final PsiParameter[] params1 = method1.getParameterList().getParameters();
296 final PsiParameter[] params2 = method2.getParameterList().getParameters();
298 final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters();
299 final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters();
300 final PsiSubstitutor classSubstitutor1 = info1.getSubstitutor(); //substitutions for method type parameters will be ignored
301 final PsiSubstitutor classSubstitutor2 = info2.getSubstitutor();
302 PsiSubstitutor methodSubstitutor1 = PsiSubstitutor.EMPTY;
303 PsiSubstitutor methodSubstitutor2 = PsiSubstitutor.EMPTY;
305 final int max = Math.max(params1.length, params2.length);
306 PsiType[] types1 = new PsiType[max];
307 PsiType[] types2 = new PsiType[max];
308 for (int i = 0; i < max; i++) {
309 PsiType type1 = params1[Math.min(i, params1.length - 1)].getType();
310 PsiType type2 = params2[Math.min(i, params2.length - 1)].getType();
311 if (applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS) {
312 if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType) {
313 type1 = ((PsiEllipsisType)type1).toArrayType();
314 type2 = ((PsiEllipsisType)type2).toArrayType();
316 else {
317 type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType)type1).getComponentType() : type1;
318 type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType)type2).getComponentType() : type2;
322 types1[i] = type1;
323 types2[i] = type2;
326 if (typeParameters1.length == 0 || typeParameters2.length == 0) {
327 if (typeParameters1.length > 0) {
328 final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getResolveHelper();
329 methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, types1, types2, resolveHelper);
331 else if (typeParameters2.length > 0) {
332 final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getResolveHelper();
333 methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, types2, types1, resolveHelper);
336 else {
337 PsiElementFactory factory = JavaPsiFacade.getInstance(myArgumentsList.getProject()).getElementFactory();
338 methodSubstitutor1 = factory.createRawSubstitutor(PsiSubstitutor.EMPTY, typeParameters1);
339 methodSubstitutor2 = factory.createRawSubstitutor(PsiSubstitutor.EMPTY, typeParameters2);
342 int[] boxingHappened = new int[2];
343 for (int i = 0; i < types1.length; i++) {
344 PsiType type1 = classSubstitutor1.substitute(methodSubstitutor1.substitute(types1[i]));
345 PsiType type2 = classSubstitutor2.substitute(methodSubstitutor2.substitute(types2[i]));
346 PsiType argType = i < myActualParameterTypes.length ? myActualParameterTypes[i] : null;
348 boxingHappened[0] += isBoxingHappened(argType, type1) ? 1 : 0;
349 boxingHappened[1] += isBoxingHappened(argType, type2) ? 1 : 0;
351 if (boxingHappened[0] == 0 && boxingHappened[1] > 0) return Specifics.FIRST;
352 if (boxingHappened[0] > 0 && boxingHappened[1] == 0) return Specifics.SECOND;
354 Specifics isMoreSpecific = null;
355 for (int i = 0; i < types1.length; i++) {
356 PsiType type1 = classSubstitutor1.substitute(methodSubstitutor1.substitute(types1[i]));
357 PsiType type2 = classSubstitutor2.substitute(methodSubstitutor2.substitute(types2[i]));
359 final Specifics specifics = type1 == null || type2 == null ? null : checkSubtyping(type1, type2);
360 if (specifics == null) continue;
361 switch (specifics) {
362 case FIRST:
363 if (isMoreSpecific == Specifics.SECOND) return Specifics.NEITHER;
364 isMoreSpecific = specifics;
365 break;
366 case SECOND:
367 if (isMoreSpecific == Specifics.FIRST) return Specifics.NEITHER;
368 isMoreSpecific = specifics;
369 break;
370 case NEITHER:
371 return Specifics.NEITHER;
375 if (isMoreSpecific == null && class1 != class2) {
376 if (class2.isInheritor(class1, true) || class1.isInterface() && !class2.isInterface()) {
377 if (MethodSignatureUtil.isSubsignature(method1.getSignature(info1.getSubstitutor()), method2.getSignature(info2.getSubstitutor()))) {
378 isMoreSpecific = Specifics.SECOND;
380 else if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC)) {
381 isMoreSpecific = Specifics.SECOND;
384 else if (class1.isInheritor(class2, true) || class2.isInterface()) {
385 if (MethodSignatureUtil.isSubsignature(method2.getSignature(info2.getSubstitutor()), method1.getSignature(info1.getSubstitutor()))) {
386 isMoreSpecific = Specifics.FIRST;
388 else if (method1.hasModifierProperty(PsiModifier.STATIC) && method2.hasModifierProperty(PsiModifier.STATIC)) {
389 isMoreSpecific = Specifics.FIRST;
393 if (isMoreSpecific == null) {
394 if (typeParameters1.length < typeParameters2.length) return Specifics.FIRST;
395 if (typeParameters1.length > typeParameters2.length) return Specifics.SECOND;
396 return Specifics.NEITHER;
399 return isMoreSpecific;
402 private PsiSubstitutor calculateMethodSubstitutor(final PsiTypeParameter[] typeParameters,
403 final PsiType[] types1,
404 final PsiType[] types2,
405 final PsiResolveHelper resolveHelper) {
406 PsiSubstitutor substitutor = resolveHelper.inferTypeArguments(typeParameters, types1, types2, PsiUtil.getLanguageLevel(myArgumentsList));
407 for (PsiTypeParameter typeParameter : typeParameters) {
408 if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
409 substitutor = substitutor.put(typeParameter, TypeConversionUtil.typeParameterErasure(typeParameter));
412 return substitutor;