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
;
31 * Created by IntelliJ IDEA.
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();
80 private void checkSpecifics(List
<CandidateInfo
> conflicts
, int applicabilityLevel
) {
81 final boolean applicable
= applicabilityLevel
> MethodCandidateInfo
.ApplicabilityLevel
.NOT_APPLICABLE
;
83 int conflictsCount
= conflicts
.size();
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
)) {
94 conflicts
.remove(conflict
);
97 conflicts
.remove(method
);
107 private static void checkAccessLevels(List
<CandidateInfo
> conflicts
) {
108 int conflictsCount
= conflicts
.size();
110 int maxCheckLevel
= -1;
111 int[] checkLevels
= new int[conflictsCount
];
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
--) {
122 if (checkLevels
[i
] < maxCheckLevel
) {
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
);
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
);
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
);
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
)) {
172 else if (!existingTypeParamAgree
&& infoTypeParamAgree
&& !PsiSuperMethodUtil
.isSuperMethod(existingMethod
, method
)) {
173 signatures
.put(signature
, info
);
174 int index
= conflicts
.indexOf(existing
);
175 conflicts
.remove(index
);
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))) {
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
) {
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
) {
232 if (level
> maxApplicabilityLevel
) {
233 maxApplicabilityLevel
= level
;
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
) {
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
{
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
) {
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();
317 type1
= type1
instanceof PsiEllipsisType ?
((PsiArrayType
)type1
).getComponentType() : type1
;
318 type2
= type2
instanceof PsiEllipsisType ?
((PsiArrayType
)type2
).getComponentType() : 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
);
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;
363 if (isMoreSpecific
== Specifics
.SECOND
) return Specifics
.NEITHER
;
364 isMoreSpecific
= specifics
;
367 if (isMoreSpecific
== Specifics
.FIRST
) return Specifics
.NEITHER
;
368 isMoreSpecific
= specifics
;
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
));