/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.lang.psi.util;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import gw.plugin.ij.lang.psi.api.auxilary.IGosuModifierList;
import gw.plugin.ij.lang.psi.api.statements.IGosuField;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuTypeDefinition;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CollectClassMembersUtil {
private static final Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> CACHED_MEMBERS = Key.create("CACHED_CLASS_MEMBERS");
private static final Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> CACHED_MEMBERS_INCLUDING_SYNTHETIC = Key.create("CACHED_MEMBERS_INCLUDING_SYNTHETIC");
private CollectClassMembersUtil() {
}
public static Map<String, List<CandidateInfo>> getAllMethods(@NotNull final PsiClass aClass, boolean includeSynthetic) {
return getCachedMembers(aClass, includeSynthetic).getSecond();
}
@NotNull
private static Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>> getCachedMembers(
@NotNull PsiClass aClass,
boolean includeSynthetic) {
Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> key =
includeSynthetic ? CACHED_MEMBERS_INCLUDING_SYNTHETIC : CACHED_MEMBERS;
CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> cachedValue = aClass.getUserData(key);
if (cachedValue == null) {
cachedValue = buildCache(aClass, includeSynthetic);
aClass.putUserData(key, cachedValue);
}
return cachedValue.getValue();
}
public static Map<String, CandidateInfo> getAllInnerClasses(@NotNull final PsiClass aClass, boolean includeSynthetic) {
return getCachedMembers(aClass, includeSynthetic).getThird();
}
public static Map<String, CandidateInfo> getAllFields(@NotNull final PsiClass aClass) {
return getCachedMembers(aClass, false).getFirst();
}
private static CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> buildCache(@NotNull final PsiClass aClass, final boolean includeSynthetic) {
return CachedValuesManager.getManager(aClass.getProject()).createCachedValue(new CachedValueProvider<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>() {
public Result<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> compute() {
Map<String, CandidateInfo> allFields = new HashMap<>();
Map<String, List<CandidateInfo>> allMethods = new HashMap<>();
Map<String, CandidateInfo> allInnerClasses = new HashMap<>();
processClass(aClass, allFields, allMethods, allInnerClasses, new HashSet<PsiClass>(), PsiSubstitutor.EMPTY, includeSynthetic);
return Result.create(Trinity.create(allFields, allMethods, allInnerClasses), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}, false);
}
private static void processClass(@NotNull PsiClass aClass, @NotNull Map<String, CandidateInfo> allFields, @NotNull Map<String, List<CandidateInfo>> allMethods, @NotNull Map<String, CandidateInfo> allInnerClasses, @NotNull Set<PsiClass> visitedClasses, @NotNull PsiSubstitutor substitutor, boolean includeSynthetic) {
if (visitedClasses.contains(aClass)) {
return;
}
visitedClasses.add(aClass);
for (PsiField field : aClass.getFields()) {
String name = field.getName();
if (!allFields.containsKey(name)) {
allFields.put(name, new CandidateInfo(field, substitutor));
} else if (hasExplicitVisibilityModifiers(field)) {
final CandidateInfo candidateInfo = allFields.get(name);
final PsiElement element = candidateInfo.getElement();
if (element instanceof IGosuField && (((IGosuField) element).getModifierList() == null ||
!(((IGosuField) element).getModifierList()).hasExplicitVisibilityModifiers()) &&
aClass == ((IGosuField) element).getContainingClass()) {
//replace property-field with field with explicit visibilityModifier
allFields.put(name, new CandidateInfo(field, substitutor));
}
}
}
for (PsiMethod method : includeSynthetic || !(aClass instanceof IGosuTypeDefinition) ? aClass.getMethods() : aClass.getMethods()) {
addMethod(allMethods, method, substitutor);
}
for (final PsiClass inner : aClass.getInnerClasses()) {
final String name = inner.getName();
if (name != null && !allInnerClasses.containsKey(name)) {
allInnerClasses.put(name, new CandidateInfo(inner, substitutor));
}
}
for (PsiClassType superType : aClass.getSuperTypes()) {
PsiClass superClass = superType.resolve();
if (superClass != null) {
final PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, substitutor);
processClass(superClass, allFields, allMethods, allInnerClasses, visitedClasses, superSubstitutor, includeSynthetic);
}
}
}
private static boolean hasExplicitVisibilityModifiers(@NotNull PsiField field) {
if (field instanceof IGosuField) {
final IGosuModifierList list = (IGosuModifierList) field.getModifierList();
return list == null || list.hasExplicitVisibilityModifiers();
} else {
return true;
}
}
private static void addMethod(@NotNull Map<String, List<CandidateInfo>> allMethods, @NotNull PsiMethod method, PsiSubstitutor substitutor) {
String name = method.getName();
List<CandidateInfo> methods = allMethods.get(name);
if (methods == null) {
methods = new ArrayList<>();
allMethods.put(name, methods);
methods.add(new CandidateInfo(method, substitutor));
} else {
methods.add(new CandidateInfo(method, substitutor));
}
}
}