/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.lang.psi.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gw.plugin.ij.lang.psi.api.statements.IGosuField;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuReferenceList;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuTypeDefinition;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuTypeDefinitionImpl;
import gw.plugin.ij.util.JavaPsiFacadeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class GosuClassImplUtil {
private static final Condition<PsiClassType> IS_GOSU_OBJECT = new Condition<PsiClassType>() {
public boolean value(@NotNull PsiClassType psiClassType) {
return
IGosuTypeDefinition.DEFAULT_BASE_CLASS_NAME.endsWith(psiClassType.getPresentableText()) &&
IGosuTypeDefinition.DEFAULT_BASE_CLASS_NAME.equals(psiClassType.getCanonicalText());
}
};
public static final String GOSU_OBJECT_SUPPORT = "gosu.lang.GosuObjectSupport";
public static final String SYNTHETIC_METHOD_IMPLEMENTATION = "GosuSyntheticMethodImplementation";
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiClassImplUtil");
private static final Key<Boolean> NAME_MAPS_BUILT_FLAG = Key.create("NAME_MAPS_BUILT_FLAG");
private static final Key<CachedValue<Map>> MAP_IN_CLASS_KEY = Key.create("MAP_KEY");
private GosuClassImplUtil() {
}
@Nullable
public static PsiClass getSuperClass(@NotNull IGosuTypeDefinition grType) {
if(grType.isAnonymous()) {
getSuperClassForAnonymousClass(grType);
}
final PsiClassType[] extendsList = grType.getExtendsListTypes();
if (extendsList.length == 0) {
return getBaseClass(grType);
}
final PsiClass superClass = extendsList[0].resolve();
return superClass != null ? superClass : getBaseClass(grType);
}
@Nullable
private static PsiClass getSuperClassForAnonymousClass(@NotNull IGosuTypeDefinition grType) {
PsiClassType baseClassReference = ((PsiAnonymousClass) grType).getBaseClassType();
PsiClass baseClass = baseClassReference.resolve();
return (baseClass != null && !baseClass.isInterface()) ? baseClass : getBaseClass(grType);
}
@Nullable
public static PsiClass getBaseClass(@NotNull IGosuTypeDefinition grType) {
if (grType.isEnum()) {
return JavaPsiFacadeUtil.findClass(grType.getProject(), CommonClassNames.JAVA_LANG_ENUM, grType.getResolveScope());
} else {
return JavaPsiFacadeUtil.findClass(grType.getProject(), CommonClassNames.JAVA_LANG_OBJECT, grType.getResolveScope());
}
}
private static final Key<CachedValue<Boolean>> HAS_GOSU_OBJECT_METHODS = Key.create("has gosu object methods");
@NotNull
public static PsiClassType[] getExtendsListTypes(@NotNull IGosuTypeDefinition gosuClass) {
if (gosuClass.isEnum()) {
PsiClassType enumSuperType = getEnumSuperType(gosuClass, JavaPsiFacade.getInstance(gosuClass.getProject()).getElementFactory());
return enumSuperType == null ? PsiClassType.EMPTY_ARRAY : new PsiClassType[]{enumSuperType};
}
final PsiClassType[] extendsTypes = getReferenceListTypes(gosuClass.getExtendsClause());
if (gosuClass.isInterface() /*|| extendsTypes.length > 0*/) {
return extendsTypes;
}
for (PsiClassType type : extendsTypes) {
final PsiClass superClass = type.resolve();
if (superClass instanceof IGosuTypeDefinition && !superClass.isInterface()) {
return extendsTypes;
}
}
PsiClass grObSupport = JavaPsiFacadeUtil.findClass(gosuClass.getProject(), GOSU_OBJECT_SUPPORT, gosuClass.getResolveScope());
if (grObSupport != null) {
return ArrayUtil.append(extendsTypes, JavaPsiFacadeUtil.getElementFactory(gosuClass.getProject()).createType(grObSupport));
}
return extendsTypes;
}
private static PsiClassType getEnumSuperType(@NotNull PsiClass psiClass, @NotNull PsiElementFactory factory) {
PsiClassType superType;
final PsiManager manager = psiClass.getManager();
final PsiClass enumClass = JavaPsiFacade.getInstance(manager.getProject()).findClass("java.lang.Enum", psiClass.getResolveScope());
if (enumClass == null) {
try {
superType = (PsiClassType)factory.createTypeFromText("java.lang.Enum", null);
}
catch (IncorrectOperationException e) {
superType = null;
}
}
else {
final PsiTypeParameter[] typeParameters = enumClass.getTypeParameters();
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
if (typeParameters.length == 1) {
substitutor = substitutor.put(typeParameters[0], factory.createType(psiClass));
}
superType = new PsiImmediateClassType(enumClass, substitutor);
}
return superType;
}
private static boolean hasGosuObjectSupportInner(@NotNull final PsiClass psiClass,
@NotNull Set<PsiClass> visited,
PsiClass gosuObjSupport,
@NotNull PsiManager manager) {
final CachedValue<Boolean> userData = psiClass.getUserData(HAS_GOSU_OBJECT_METHODS);
if (userData != null && userData.getValue() != null) {
return userData.getValue();
}
if (manager.areElementsEquivalent(gosuObjSupport, psiClass)) {
return true;
}
final PsiClassType[] supers;
if (psiClass instanceof IGosuTypeDefinition) {
supers = getReferenceListTypes(((IGosuTypeDefinition) psiClass).getExtendsClause());
} else {
supers = psiClass.getExtendsListTypes();
}
boolean result = false;
for (PsiClassType superType : supers) {
PsiClass aSuper = superType.resolve();
if (aSuper == null || visited.contains(aSuper)) {
continue;
}
visited.add(aSuper);
if (hasGosuObjectSupportInner(aSuper, visited, gosuObjSupport, manager)) {
result = true;
break;
}
}
final boolean finalResult = result;
psiClass.putUserData(HAS_GOSU_OBJECT_METHODS,
CachedValuesManager.getManager(manager.getProject()).createCachedValue(new CachedValueProvider<Boolean>() {
public Result<Boolean> compute() {
return Result.create(finalResult, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
}, false));
return finalResult;
}
@NotNull
public static PsiClassType[] getImplementsListTypes(@NotNull IGosuTypeDefinition grType) {
final PsiClassType[] implementsTypes = getReferenceListTypes(grType.getImplementsClause());
if (!grType.isInterface() &&
!ContainerUtil.or(implementsTypes, IS_GOSU_OBJECT) &&
!ContainerUtil.or(getReferenceListTypes(grType.getExtendsClause()), IS_GOSU_OBJECT)) {
return ArrayUtil.append(implementsTypes, getGosuObjectType(grType));
}
return implementsTypes;
}
@NotNull
private static PsiClassType getGosuObjectType(@NotNull IGosuTypeDefinition grType) {
return JavaPsiFacadeUtil.getElementFactory(grType.getProject())
.createTypeByFQClassName(IGosuTypeDefinition.DEFAULT_BASE_CLASS_NAME, grType.getResolveScope());
}
@NotNull
public static PsiClassType[] getSuperTypes(@NotNull IGosuTypeDefinition grType) {
if(grType.isAnonymous()) {
return getSuperTypesForAnonymousClass(grType);
}
PsiClassType[] extendsList = grType.getExtendsListTypes();
if (extendsList.length == 0) {
extendsList = new PsiClassType[]{createBaseClassType(grType)};
}
return ArrayUtil.mergeArrays(extendsList, grType.getImplementsListTypes(), PsiClassType.class);
}
@NotNull
private static PsiClassType[] getSuperTypesForAnonymousClass(@NotNull IGosuTypeDefinition grType) {
PsiClassType baseClassReference = ((PsiAnonymousClass) grType).getBaseClassType();
PsiClass baseClass = baseClassReference.resolve();
if (baseClass != null) {
if (baseClass.isInterface()) {
return new PsiClassType[] {createBaseClassType(grType), baseClassReference};
} else {
return new PsiClassType[] {baseClassReference};
}
} else {
return new PsiClassType[]{createBaseClassType(grType)};
}
}
@NotNull
public static PsiClassType createBaseClassType(@NotNull IGosuTypeDefinition grType) {
if (grType.isEnum()) {
return JavaPsiFacadeUtil.getElementFactory(grType.getProject())
.createTypeByFQClassName(CommonClassNames.JAVA_LANG_ENUM, grType.getResolveScope());
} else {
return JavaPsiFacadeUtil.getElementFactory(grType.getProject())
.createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, grType.getResolveScope());
}
}
@NotNull
public static PsiMethod[] getAllMethods(@NotNull IGosuTypeDefinition grType) {
List<PsiMethod> allMethods = new ArrayList<>();
getAllMethodsInner(grType, allMethods, new HashSet<PsiClass>());
return allMethods.toArray(new PsiMethod[allMethods.size()]);
}
private static void getAllMethodsInner(@NotNull PsiClass clazz, @NotNull List<PsiMethod> allMethods, @NotNull HashSet<PsiClass> visited) {
if (visited.contains(clazz)) {
return;
}
visited.add(clazz);
ContainerUtil.addAll(allMethods, clazz.getMethods());
final PsiField[] fields = clazz.getFields();
for (PsiField field : fields) {
if (field instanceof IGosuField) {
final IGosuField gosuField = (IGosuField) field;
//## todo:
// if( gosuField.isProperty() )
// {
// PsiMethod[] getters = gosuField.getGetters();
// if( getters.length > 0 )
// {
// ContainerUtil.addAll( allMethods, getters );
// }
// PsiMethod setter = gosuField.getSetter();
// if( setter != null )
// {
// allMethods.add( setter );
// }
// }
}
}
final PsiClass[] supers = clazz.getSupers();
for (PsiClass aSuper : supers) {
getAllMethodsInner(aSuper, allMethods, visited);
}
}
private static PsiClassType[] getReferenceListTypes(@Nullable IGosuReferenceList list) {
if (list == null) {
return PsiClassType.EMPTY_ARRAY;
}
return list.getReferenceTypes();
}
public static PsiClass[] getInterfaces(@NotNull IGosuTypeDefinition grType) {
final PsiClassType[] implementsListTypes = grType.getImplementsListTypes();
List<PsiClass> result = new ArrayList<>(implementsListTypes.length);
for (PsiClassType type : implementsListTypes) {
final PsiClass psiClass = type.resolve();
if (psiClass != null) {
result.add(psiClass);
}
}
return result.toArray(new PsiClass[result.size()]);
}
@NotNull
public static PsiClass[] getSupers(@NotNull IGosuTypeDefinition grType) {
PsiClassType[] superTypes = grType.getSuperTypes();
List<PsiClass> result = new ArrayList<>();
for (PsiClassType superType : superTypes) {
PsiClass superClass = superType.resolve();
if (superClass != null) {
result.add(superClass);
}
}
return result.toArray(new PsiClass[result.size()]);
}
public static boolean processDeclarations(@NotNull IGosuTypeDefinition grType,
@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
//## todo:
return true;
// for( final PsiTypeParameter typeParameter : grType.getTypeParameters() )
// {
// if( !ResolveUtil.processElement( processor, typeParameter, state ) )
// {
// return false;
// }
// }
//
// NameHint nameHint = processor.getHint( NameHint.KEY );
// //todo [DIANA] look more carefully
// String name = nameHint == null ? null : nameHint.getName( state );
// ClassHint classHint = processor.getHint( ClassHint.KEY );
// final PsiSubstitutor substitutor = state.get( PsiSubstitutor.KEY );
// final PsiElementFactory factory = JavaPsiFacade.getElementFactory( place.getProject() );
//
// if( classHint == null || classHint.shouldProcess( ClassHint.ResolveKind.PROPERTY ) )
// {
// Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields( grType );
// if( name != null )
// {
// CandidateInfo fieldInfo = fieldsMap.get( name );
// if( fieldInfo != null )
// {
// final PsiField field = (PsiField)fieldInfo.getElement();
// if( !isSameDeclaration( place, field ) )
// { //the same variable declaration
// final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
// .obtainFinalSubstitutor( field.getContainingClass(), fieldInfo.getSubstitutor(), grType, substitutor, place, factory );
// if( !processor.execute( field, state.put( PsiSubstitutor.KEY, finalSubstitutor ) ) )
// {
// return false;
// }
// }
// }
// }
// else
// {
// for( CandidateInfo info : fieldsMap.values() )
// {
// final PsiField field = (PsiField)info.getElement();
// if( !isSameDeclaration( place, field ) )
// { //the same variable declaration
// final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
// .obtainFinalSubstitutor( field.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory );
// if( !processor.execute( field, state.put( PsiSubstitutor.KEY, finalSubstitutor ) ) )
// {
// return false;
// }
// }
// }
// }
// }
//
// if( classHint == null || classHint.shouldProcess( ClassHint.ResolveKind.METHOD ) )
// {
// Map<String, List<CandidateInfo>> methodsMap = CollectClassMembersUtil.getAllMethods( grType, true );
// boolean isPlaceGosu = place.getLanguage() == GosuFileType.GOSU_FILE_TYPE.getLanguage();
// if( name == null )
// {
// for( List<CandidateInfo> list : methodsMap.values() )
// {
// for( CandidateInfo info : list )
// {
// PsiMethod method = (PsiMethod)info.getElement();
// if( !isSameDeclaration( place, method ) && isMethodVisible( isPlaceGosu, method ) )
// {
// final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
// .obtainFinalSubstitutor( method.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory );
// if( !processor.execute( method, state.put( PsiSubstitutor.KEY, finalSubstitutor ) ) )
// {
// return false;
// }
// }
// }
// }
// }
// else
// {
// List<CandidateInfo> byName = methodsMap.get( name );
// if( byName != null )
// {
// for( CandidateInfo info : byName )
// {
// PsiMethod method = (PsiMethod)info.getElement();
// if( !isSameDeclaration( place, method ) && isMethodVisible( isPlaceGosu, method ) )
// {
// final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
// .obtainFinalSubstitutor( method.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory );
// if( !processor.execute( method, state.put( PsiSubstitutor.KEY, finalSubstitutor ) ) )
// {
// return false;
// }
// }
// }
// }
// }
// }
//
// if( !isSuperClassReferenceResolving( grType, lastParent ) )
// {
// if( classHint == null || classHint.shouldProcess( ClassHint.ResolveKind.CLASS ) )
// {
// for( CandidateInfo info : CollectClassMembersUtil.getAllInnerClasses( grType, false ).values() )
// {
// final PsiClass innerClass = (PsiClass)info.getElement();
// assert innerClass != null;
// final String innerClassName = innerClass.getName();
// if( nameHint != null && !innerClassName.equals( nameHint.getName( state ) ) )
// {
// continue;
// }
//
// if( !processor.execute( innerClass, state ) )
// {
// return false;
// }
// }
// }
// }
//
//
// return true;
}
// private static boolean isSuperClassReferenceResolving( IGosuTypeDefinition grType, PsiElement lastParent )
// {
// return lastParent instanceof IGosuReferenceList ||
// grType.isAnonymous() && lastParent == ((IGosuAnonymousClassDefinition)grType).getBaseClassReferenceGosu();
// }
private static boolean isSameDeclaration(@Nullable PsiElement place, PsiElement element) {
if (!(element instanceof IGosuField)) {
return false;
}
while (place != null) {
place = place.getParent();
if (place == element) {
return true;
}
}
return false;
}
private static boolean isMethodVisible(boolean isPlaceGosu, PsiMethod method) {
return isPlaceGosu;
}
@Nullable
public static PsiMethod findMethodBySignature(@NotNull IGosuTypeDefinition grType, @NotNull PsiMethod patternMethod, boolean checkBases) {
final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
for (PsiMethod method : findMethodsByName(grType, patternMethod.getName(), checkBases, false)) {
final PsiClass clazz = method.getContainingClass();
if (clazz == null) {
continue;
}
PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(clazz, grType, PsiSubstitutor.EMPTY);
if (superSubstitutor == null) {
continue;
}
final MethodSignature signature = method.getSignature(superSubstitutor);
if (signature.equals(patternSignature)) {
return method;
}
}
return null;
}
private static PsiMethod[] findMethodsByName(@NotNull IGosuTypeDefinition grType,
@NotNull String name,
boolean checkBases,
boolean includeSyntheticAccessors) {
List<PsiMethod> result = new ArrayList<>();
if (!checkBases) {
for (PsiMethod method : includeSyntheticAccessors ? grType.getMethods() : grType.getMethods()) {
if (name.equals(method.getName())) {
result.add(method);
}
}
} else {
PsiClass type = grType;
while (type != null) {
for (PsiMethod method : includeSyntheticAccessors ? type.getMethods() : type.getMethods()) {
if (name.equals(method.getName())) {
result.add(method);
}
}
type = type.getSuperClass();
}
}
return result.toArray(new PsiMethod[result.size()]);
}
@NotNull
public static PsiMethod[] findMethodsBySignature(@NotNull IGosuTypeDefinition grType, @NotNull PsiMethod patternMethod, boolean checkBases) {
return findMethodsBySignature(grType, patternMethod, checkBases, true);
}
@NotNull
public static PsiMethod[] findCodeMethodsBySignature(@NotNull IGosuTypeDefinition grType, @NotNull PsiMethod patternMethod, boolean checkBases) {
return findMethodsBySignature(grType, patternMethod, checkBases, false);
}
@NotNull
public static PsiMethod[] findMethodsByName(@NotNull IGosuTypeDefinition grType, @NotNull @NonNls String name, boolean checkBases) {
return findMethodsByName(grType, name, checkBases, true);
}
private static PsiMethod[] findMethodsBySignature(@NotNull IGosuTypeDefinition grType,
@NotNull PsiMethod patternMethod,
boolean checkBases,
boolean includeSynthetic) {
ArrayList<PsiMethod> result = new ArrayList<>();
final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
for (PsiMethod method : findMethodsByName(grType, patternMethod.getName(), checkBases, includeSynthetic)) {
final PsiClass clazz = method.getContainingClass();
if (clazz == null) {
continue;
}
PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(clazz, grType, PsiSubstitutor.EMPTY);
if (superSubstitutor == null) {
continue;
}
final MethodSignature signature = method.getSignature(superSubstitutor);
if (signature.equals(patternSignature)) //noinspection unchecked
{
result.add(method);
}
}
return result.toArray(new PsiMethod[result.size()]);
}
@NotNull
public static PsiMethod[] findCodeMethodsByName(@NotNull IGosuTypeDefinition grType, @NotNull @NonNls String name, boolean checkBases) {
return findMethodsByName(grType, name, checkBases, false);
}
@NotNull
public static List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName(@NotNull IGosuTypeDefinition grType,
String name,
boolean checkBases) {
final ArrayList<Pair<PsiMethod, PsiSubstitutor>> result = new ArrayList<>();
if (!checkBases) {
final PsiMethod[] methods = grType.findMethodsByName(name, false);
for (PsiMethod method : methods) {
result.add(new Pair<>(method, PsiSubstitutor.EMPTY));
}
} else {
final Map<String, List<CandidateInfo>> map = CollectClassMembersUtil.getAllMethods(grType, true);
final List<CandidateInfo> candidateInfos = map.get(name);
if (candidateInfos != null) {
for (CandidateInfo info : candidateInfos) {
final PsiElement element = info.getElement();
result.add(new Pair<>((PsiMethod) element, info.getSubstitutor()));
}
}
}
return result;
}
@NotNull
public static List<Pair<PsiMethod, PsiSubstitutor>> getAllMethodsAndTheirSubstitutors(@NotNull IGosuTypeDefinition grType) {
final Map<String, List<CandidateInfo>> allMethodsMap = CollectClassMembersUtil.getAllMethods(grType, true);
List<Pair<PsiMethod, PsiSubstitutor>> result = new ArrayList<>();
for (List<CandidateInfo> infos : allMethodsMap.values()) {
for (CandidateInfo info : infos) {
result.add(new Pair<>((PsiMethod) info.getElement(), info.getSubstitutor()));
}
}
return result;
}
@Nullable
public static PsiField findFieldByName(@NotNull IGosuTypeDefinition grType, @NotNull String name, boolean checkBases) {
if (!checkBases) {
for (PsiField field : grType.getFields()) {
if (name.equals(field.getName())) {
return field;
}
}
return null;
}
Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields(grType);
final CandidateInfo info = fieldsMap.get(name);
return info == null ? null : (PsiField) info.getElement();
}
@NotNull
public static PsiField[] getAllFields(@NotNull IGosuTypeDefinition grType) {
Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields(grType);
return ContainerUtil.map2Array(fieldsMap.values(), PsiField.class, new Function<CandidateInfo, PsiField>() {
@Nullable
public PsiField fun(@NotNull CandidateInfo entry) {
return (PsiField) entry.getElement();
}
});
}
public static boolean isClassEquivalentTo(GosuTypeDefinitionImpl definition, PsiElement another) {
return PsiClassImplUtil.isClassEquivalentTo(definition, another);
}
}