private Specifics isMoreSpecific(@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;
  }
private Specifics isMoreSpecific(@NotNull MethodCandidateInfo info1,
                                   @NotNull MethodCandidateInfo info2,
                                   @MethodCandidateInfo.ApplicabilityLevelConstant int applicabilityLevel,
                                   Map<MethodCandidateInfo, PsiSubstitutor> map,
                                   int offset)