Specifics (@NotNull MethodCandidateInfo info1, @NotNull MethodCandidateInfo info2, @MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel, Map<MethodCandidateInfo, PsiSubstitutor> map, int offset) { PsiMethod method1 = info1.getElement(); PsiMethod method2 = info2.getElement(); final PsiClass class1 = method1.getContainingClass(); final PsiClass class2 = method2.getContainingClass(); final PsiParameter[] params1 = method1.getParameterList().getParameters(); final PsiParameter[] params2 = method2.getParameterList().getParameters(); final PsiTypeParameter[] typeParameters1 = method1.getTypeParameters(); final PsiTypeParameter[] typeParameters2 = method2.getTypeParameters(); final PsiSubstitutor classSubstitutor1 = getSubstitutor(info1, map); //substitutions for method type parameters will be ignored final PsiSubstitutor classSubstitutor2 = getSubstitutor(info2, map); //process all arguments of varargs call //todo check method reference actual params length final int argsLength = myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) && (method1.isVarArgs() || method2.isVarArgs()) ? getActualParametersLength() : 0; final int max = Math.max(Math.max(params1.length, params2.length), argsLength); PsiType[] types1 = PsiType.createArray(max); PsiType[] types2 = PsiType.createArray(max); boolean[] varargs = new boolean[max]; final boolean varargsPosition = applicabilityLevel == MethodCandidateInfo.ApplicabilityLevel.VARARGS; for (int i = 0; i < max; i++) { ProgressManager.checkCanceled(); PsiType type1 = params1.length > 0 ? params1[Math.min(i, params1.length - 1)].getType() : null; PsiType type2 = params2.length > 0 ? params2[Math.min(i, params2.length - 1)].getType() : null; if (varargsPosition) { if (type1 instanceof PsiEllipsisType && type2 instanceof PsiEllipsisType && params1.length == params2.length && (class1 != null && !JavaVersionService.getInstance().isAtLeast(class1, JavaSdkVersion.JDK_1_7) || ((PsiArrayType)type1).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || ((PsiArrayType)type2).getComponentType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT))) { type1 = ((PsiEllipsisType)type1).toArrayType(); type2 = ((PsiEllipsisType)type2).toArrayType(); } else { type1 = type1 instanceof PsiEllipsisType ? ((PsiArrayType)type1).getComponentType() : type1; type2 = type2 instanceof PsiEllipsisType ? ((PsiArrayType)type2).getComponentType() : type2; varargs[i] = true; } } types1[i] = type1; types2[i] = type2; } boolean sameBoxing = true; boolean[] boxingHappened = new boolean [2]; final PsiExpression[] args = myArgumentsList instanceof PsiExpressionList ? ((PsiExpressionList)myArgumentsList).getExpressions() : null; for (int i = 0; i < types1.length; i++) { ProgressManager.checkCanceled(); if (varargs[i]) continue; final PsiExpression arg = args != null && i < args.length ? args[i] : null; final PsiType argType = myActualParameterTypes != null && i + offset < getActualParametersLength() ? myActualParameterTypes[i + offset] : null; if (arg == null && argType == null) continue; boolean boxingInFirst = false; if (isBoxingUsed(classSubstitutor1.substitute(types1[i]), argType, arg)) { boxingHappened[0] = true; boxingInFirst = true; } boolean boxingInSecond = false; if (isBoxingUsed(classSubstitutor2.substitute(types2[i]), argType, arg)) { boxingHappened[1] = true; boxingInSecond = true; } sameBoxing &= boxingInFirst == boxingInSecond; } if (!boxingHappened[0] && boxingHappened[1]) return Specifics.FIRST; if (boxingHappened[0] && !boxingHappened[1]) return Specifics.SECOND; if (sameBoxing) { final PsiSubstitutor siteSubstitutor = getSiteSubstitutor(info1).putAll(getSiteSubstitutor(info2)); final PsiType[] types2AtSite = typesAtSite(types2, siteSubstitutor); final PsiType[] types1AtSite = typesAtSite(types1, siteSubstitutor); final PsiSubstitutor methodSubstitutor1 = calculateMethodSubstitutor(typeParameters1, method1, siteSubstitutor, types1, types2AtSite, myLanguageLevel); boolean applicable12 = isApplicableTo(types2AtSite, method1, myLanguageLevel, varargsPosition, methodSubstitutor1, method2, siteSubstitutor); final PsiSubstitutor methodSubstitutor2 = calculateMethodSubstitutor(typeParameters2, method2, siteSubstitutor, types2, types1AtSite, myLanguageLevel); boolean applicable21 = isApplicableTo(types1AtSite, method2, myLanguageLevel, varargsPosition, methodSubstitutor2, method1, siteSubstitutor); if (!myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { final boolean typeArgsApplicable12 = GenericsUtil.isTypeArgumentsApplicable(typeParameters1, methodSubstitutor1, myArgumentsList, !applicable21); final boolean typeArgsApplicable21 = GenericsUtil.isTypeArgumentsApplicable(typeParameters2, methodSubstitutor2, myArgumentsList, !applicable12); if (!typeArgsApplicable12) { applicable12 = false; } if (!typeArgsApplicable21) { applicable21 = false; } } if (applicable12 || applicable21) { if (applicable12 && !applicable21) return Specifics.SECOND; if (!applicable12) return Specifics.FIRST; //from 15.12.2.5 Choosing the Most Specific Method: concrete = nonabstract or default final boolean abstract1 = method1.hasModifierProperty(PsiModifier.ABSTRACT) || method1.hasModifierProperty(PsiModifier.DEFAULT); final boolean abstract2 = method2.hasModifierProperty(PsiModifier.ABSTRACT) || method2.hasModifierProperty(PsiModifier.DEFAULT); if (abstract1 && !abstract2) { return Specifics.SECOND; } if (abstract2 && !abstract1) { return Specifics.FIRST; } if (abstract1 && MethodSignatureUtil.areOverrideEquivalent(method1, method2)) { // abstract1 && abstract2 final PsiType returnType1 = siteSubstitutor.substitute(method1.getReturnType()); final PsiType returnType2 = siteSubstitutor.substitute(method2.getReturnType()); if (returnType1 != null && returnType2 != null && returnType1.isAssignableFrom(returnType2)) { return Specifics.SECOND; } return Specifics.FIRST; } } } else if (varargsPosition) { final PsiType lastParamType1 = classSubstitutor1.substitute(types1[types1.length - 1]); final PsiType lastParamType2 = classSubstitutor2.substitute(types2[types1.length - 1]); final boolean assignable1 = TypeConversionUtil.isAssignable(lastParamType2, lastParamType1); final boolean assignable2 = TypeConversionUtil.isAssignable(lastParamType1, lastParamType2); if (assignable1 && !assignable2) { return Specifics.FIRST; } if (assignable2 && !assignable1) { return Specifics.SECOND; } } if (class1 != class2 && (method1.hasModifierProperty(PsiModifier.STATIC) || method2.hasModifierProperty(PsiModifier.STATIC))) { if (class2.isInheritor(class1, true)) { if (MethodSignatureUtil.isSubsignature(method1.getSignature(classSubstitutor1), method2.getSignature(classSubstitutor2))) { return Specifics.SECOND; } } else if (class1.isInheritor(class2, true)) { if (MethodSignatureUtil.isSubsignature(method2.getSignature(classSubstitutor2), method1.getSignature(classSubstitutor1))) { return Specifics.FIRST; } } } final boolean varargs1 = info1.isVarargs(); final boolean varargs2 = info2.isVarargs(); if (varargs1 != varargs2) { return varargs1 ? Specifics.SECOND : Specifics.FIRST; } return Specifics.NEITHER; }