blob: c64baf294ce6236f03772f81675973a8ff12703a [file] [log] [blame]
package com.intellij.codeInsight.generation;
import com.intellij.codeInsight.MemberImplementorExplorer;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.NullableLazyValue;
import com.intellij.openapi.util.VolatileNullableLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class OverrideImplementExploreUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.OverrideImplementExploreUtil");
@NotNull
public static Collection<CandidateInfo> getMethodsToOverrideImplement(PsiClass aClass, boolean toImplement) {
return getMapToOverrideImplement(aClass, toImplement).values();
}
@NotNull
public static Collection<MethodSignature> getMethodSignaturesToImplement(@NotNull PsiClass aClass) {
return getMapToOverrideImplement(aClass, true).keySet();
}
@NotNull
public static Collection<MethodSignature> getMethodSignaturesToOverride(@NotNull PsiClass aClass) {
if (aClass.isAnnotationType()) return Collections.emptySet();
return getMapToOverrideImplement(aClass, false).keySet();
}
@NotNull
public static Map<MethodSignature, CandidateInfo> getMapToOverrideImplement(PsiClass aClass, boolean toImplement) {
return getMapToOverrideImplement(aClass, toImplement, true);
}
@NotNull
public static Map<MethodSignature, CandidateInfo> getMapToOverrideImplement(PsiClass aClass, boolean toImplement, boolean skipImplemented) {
Map<MethodSignature, PsiMethod> abstracts = new LinkedHashMap<MethodSignature,PsiMethod>();
Map<MethodSignature, PsiMethod> finals = new LinkedHashMap<MethodSignature,PsiMethod>();
Map<MethodSignature, PsiMethod> concretes = new LinkedHashMap<MethodSignature,PsiMethod>();
PsiUtilCore.ensureValid(aClass);
Collection<HierarchicalMethodSignature> allMethodSigs = aClass.getVisibleSignatures();
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper();
for (HierarchicalMethodSignature signature : allMethodSigs) {
PsiMethod method = signature.getMethod();
PsiUtilCore.ensureValid(method);
if (method.hasModifierProperty(PsiModifier.STATIC) || !resolveHelper.isAccessible(method, aClass, aClass)) continue;
PsiClass hisClass = method.getContainingClass();
if (hisClass == null) continue;
// filter non-immediate super constructors
if (method.isConstructor() && (!aClass.isInheritor(hisClass, false) || aClass instanceof PsiAnonymousClass || aClass.isEnum())) {
continue;
}
// filter already implemented
if (skipImplemented && MethodSignatureUtil.findMethodBySignature(aClass, signature, false) != null) {
continue;
}
if (method.hasModifierProperty(PsiModifier.FINAL)) {
finals.put(signature, method);
continue;
}
Map<MethodSignature, PsiMethod> map = hisClass.isInterface() || method.hasModifierProperty(PsiModifier.ABSTRACT) ? abstracts : concretes;
fillMap(signature, method, map);
if (isDefaultMethod(aClass, method)) {
fillMap(signature, method, concretes);
}
}
final Map<MethodSignature, CandidateInfo> result = new TreeMap<MethodSignature,CandidateInfo>(new MethodSignatureComparator());
if (toImplement || aClass.isInterface()) {
collectMethodsToImplement(aClass, abstracts, finals, concretes, result);
}
else {
for (Map.Entry<MethodSignature, PsiMethod> entry : concretes.entrySet()) {
MethodSignature signature = entry.getKey();
PsiMethod concrete = entry.getValue();
if (finals.get(signature) == null) {
PsiMethod abstractOne = abstracts.get(signature);
if (abstractOne == null || !abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true) ||
CommonClassNames.JAVA_LANG_OBJECT.equals(concrete.getContainingClass().getQualifiedName())) {
PsiSubstitutor subst = correctSubstitutor(concrete, signature.getSubstitutor());
CandidateInfo info = new CandidateInfo(concrete, subst);
result.put(signature, info);
}
}
}
}
return result;
}
private static boolean isDefaultMethod(PsiClass aClass, PsiMethod method) {
return method.hasModifierProperty(PsiModifier.DEFAULT) &&
PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8);
}
private static void fillMap(HierarchicalMethodSignature signature, PsiMethod method, Map<MethodSignature, PsiMethod> map) {
final PsiMethod other = map.get(signature);
if (other == null || preferLeftForImplement(method, other)) {
map.put(signature, method);
}
}
public interface MemberImplementorExplorersProvider {
MemberImplementorExplorer[] getExplorers();
}
private static final NullableLazyValue<MemberImplementorExplorersProvider> ourExplorersProvider = new VolatileNullableLazyValue<MemberImplementorExplorersProvider>() {
@Override
protected MemberImplementorExplorersProvider compute() {
return ServiceManager.getService(MemberImplementorExplorersProvider.class);
}
};
public static void collectMethodsToImplement(PsiClass aClass,
Map<MethodSignature, PsiMethod> abstracts,
Map<MethodSignature, PsiMethod> finals,
Map<MethodSignature, PsiMethod> concretes,
Map<MethodSignature, CandidateInfo> result) {
for (Map.Entry<MethodSignature, PsiMethod> entry : abstracts.entrySet()) {
MethodSignature signature = entry.getKey();
PsiMethod abstractOne = entry.getValue();
PsiMethod concrete = concretes.get(signature);
if (concrete == null
|| PsiUtil.getAccessLevel(concrete.getModifierList()) < PsiUtil.getAccessLevel(abstractOne.getModifierList())
|| !abstractOne.getContainingClass().isInterface() && abstractOne.getContainingClass().isInheritor(concrete.getContainingClass(), true)
|| isDefaultMethod(aClass, abstractOne)) {
if (finals.get(signature) == null) {
PsiSubstitutor subst = correctSubstitutor(abstractOne, signature.getSubstitutor());
CandidateInfo info = new CandidateInfo(abstractOne, subst);
result.put(signature, info);
}
}
}
MemberImplementorExplorersProvider explorersProvider = ourExplorersProvider.getValue();
if (explorersProvider != null) {
for (final MemberImplementorExplorer implementor : explorersProvider.getExplorers()) {
for (final PsiMethod method : implementor.getMethodsToImplement(aClass)) {
MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), method.getParameterList(),
method.getTypeParameterList(), PsiSubstitutor.EMPTY,
method.isConstructor());
CandidateInfo info = new CandidateInfo(method, PsiSubstitutor.EMPTY);
result.put(signature, info);
}
}
}
}
private static boolean preferLeftForImplement(PsiMethod left, PsiMethod right) {
if (PsiUtil.getAccessLevel(left.getModifierList()) > PsiUtil.getAccessLevel(right.getModifierList())) return true;
if (!left.getContainingClass().isInterface()) return true;
if (!right.getContainingClass().isInterface()) return false;
// implement annotated method
PsiAnnotation[] leftAnnotations = left.getModifierList().getAnnotations();
PsiAnnotation[] rightAnnotations = right.getModifierList().getAnnotations();
return leftAnnotations.length > rightAnnotations.length;
}
public static class MethodSignatureComparator implements Comparator<MethodSignature> {
// signatures should appear in the order of declaration
@Override
public int compare(MethodSignature o1, MethodSignature o2) {
if (o1 instanceof MethodSignatureBackedByPsiMethod && o2 instanceof MethodSignatureBackedByPsiMethod) {
PsiMethod m1 = ((MethodSignatureBackedByPsiMethod)o1).getMethod();
PsiMethod m2 = ((MethodSignatureBackedByPsiMethod)o2).getMethod();
PsiClass c1 = m1.getContainingClass();
PsiClass c2 = m2.getContainingClass();
if (c1 != null && c2 != null) {
if (c1 == c2) {
final List<PsiMethod> methods = Arrays.asList(c1.getMethods());
return methods.indexOf(m1) - methods.indexOf(m2);
}
if (c1.isInheritor(c2, true)) return -1;
if (c2.isInheritor(c1, true)) return 1;
return StringUtil.notNullize(c1.getName()).compareTo(StringUtil.notNullize(c2.getName()));
}
return m1.getTextOffset() - m2.getTextOffset();
}
return 0;
}
}
public static PsiSubstitutor correctSubstitutor(PsiMethod method, PsiSubstitutor substitutor) {
PsiClass hisClass = method.getContainingClass();
PsiTypeParameter[] typeParameters = method.getTypeParameters();
if (typeParameters.length > 0) {
if (PsiUtil.isRawSubstitutor(hisClass, substitutor)) {
substitutor = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createRawSubstitutor(substitutor, typeParameters);
}
}
return substitutor;
}
}