/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.lang.psi.impl.resolvers; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import gw.fs.IFile; import gw.lang.parser.IBlockClass; import gw.lang.reflect.*; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.java.JavaTypes; import gw.plugin.ij.lang.psi.IGosuFileBase; import gw.plugin.ij.lang.psi.api.IFileShadowingResolver; import gw.plugin.ij.lang.psi.api.ITypeResolver; import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuAnonymousClassDefinition; import gw.plugin.ij.lang.psi.impl.CustomPsiClassCache; import gw.plugin.ij.lang.psi.impl.GosuProgramFileImpl; import gw.plugin.ij.lang.psi.impl.GosuScratchpadFileImpl; import gw.plugin.ij.lang.psi.impl.expressions.GosuBlockExpressionImpl; import gw.plugin.ij.util.ExecutionUtil; import gw.plugin.ij.util.FileUtil; import gw.plugin.ij.util.SafeCallable; import gw.plugin.ij.util.InjectedElementEditor; import gw.plugin.ij.util.JavaPsiFacadeUtil; import gw.plugin.ij.util.TypeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; public class DefaultTypeResolver implements ITypeResolver { @Nullable public PsiElement resolveType(@NotNull final IType theType, @NotNull final PsiElement context) { if (theType instanceof ICompoundType) { return null; } ProgressManager.checkCanceled(); return ExecutionUtil.execute(new SafeCallable<PsiElement>(context) { @Nullable public PsiElement execute() throws Exception { IType type = theType; if (type instanceof IMetaType) { return null; } type = TypeUtil.getConcreteType(type); if (type instanceof ITypeVariableType) { return PsiTypeResolver.resolveTypeVariable((ITypeVariableType) type, context); } else if (type instanceof ILocationAwareFeature) { ILocationAwareFeature feature = (ILocationAwareFeature) type; LocationInfo location = feature.getLocationInfo(); return PsiFeatureResolver.resolveFeatureAtLocation(context, location); } else { return resolveType(type.getName(), context); } } }); } @Nullable public PsiClass resolveType(@Nullable final String strFullName, @NotNull final PsiElement ctx) { if (strFullName == null) { return null; } return ExecutionUtil.execute(new SafeCallable<PsiClass>(ctx) { @Nullable public PsiClass execute() throws Exception { String fqn = getNonProxyClassName(strFullName); PsiElement aClass = resolveJavaType(fqn, ctx.getResolveScope()); if (aClass == null) { if (GosuScratchpadFileImpl.FQN.equals(fqn)) { GosuScratchpadFileImpl scratchpadFile = GosuScratchpadFileImpl.instance(ctx.getProject()); aClass = scratchpadFile == null ? null : scratchpadFile.getPsiClass(); } else if (fqn.startsWith(GosuScratchpadFileImpl.FQN)) { aClass = resolveScratchpadInnerClass(fqn, ctx); } else { IType type = TypeSystem.getByFullNameIfValid(fqn); aClass = resolveGosuType(type, ctx.getProject(), ctx, ctx.getResolveScope()); } } if (aClass == null && strFullName.contains(FileUtil.MAGIC_INJECTED_SUFFIX)) { PsiClass psiClass = ((IGosuFileBase) ctx.getContainingFile()).getPsiClass(); //do not uncomment this "if" as it return false for injected fragment and corresponding injected editor, but should return true // if (strFullName.equals(psiClass.getQualifiedName())) { final GosuProgramFileImpl program = (GosuProgramFileImpl) InjectedElementEditor.getOriginalFile(psiClass.getContainingFile()); final PsiClass[] classes = program != null ? program.getClasses() : new PsiClass[]{psiClass}; aClass = classes.length > 0 ? classes[0] : psiClass; } } return aClass instanceof PsiClass ? (PsiClass) aClass : null; } }); } // private @Nullable private static PsiElement resolveTypeImpl(@Nullable IType type, Project project, PsiElement ctx, @NotNull GlobalSearchScope scope) { if (type == null) { return null; } final String strFullName = getNonProxyClassName(type.getName()); PsiElement aClass = resolveJavaType(strFullName, scope); if (aClass == null) { aClass = resolveGosuType(type, project, ctx, scope); } return aClass; } private static String getNonProxyClassName(String strFullName) { return IGosuClass.ProxyUtil.isProxyClass(strFullName) ? IGosuClass.ProxyUtil.getNameSansProxy(strFullName) : strFullName; } @Nullable private static PsiElement resolveJavaType(@NotNull String strFullName, @NotNull GlobalSearchScope scope) { final Project project = scope.getProject(); PsiElement aClass; if (JavaTypes.IGOSU_OBJECT().getName().equals(strFullName)) { aClass = JavaPsiFacadeUtil.getElementFactory(project).createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, scope).resolve(); } else { PsiClass[] classes = JavaPsiFacadeUtil.findClasses(project, strFullName, scope); if(classes.length == 1) { aClass = classes[0]; } else { aClass = getTheRightClass(classes); } } if (aClass == null) { // this will find Gosu classes in jar files // the search is done this way to avoid the results being filtered out according to Java rules try { Collection<PsiClass> classes = StubIndex.getInstance().get(JavaFullClassNameIndex.getInstance().getKey(), strFullName.hashCode(), project, scope); if (!classes.isEmpty()) { aClass = classes.iterator().next(); } } catch (IndexNotReadyException e) { // Nothing to do } } return aClass; } private static PsiElement getTheRightClass(PsiClass[] classes) { if(classes.length > 0) { VirtualFile virtualFile = classes[0].getContainingFile().getVirtualFile(); final FileShadowingResolverExtensionBean[] extensions = Extensions.getExtensions(FileShadowingResolverExtensionBean.EP_NAME); IFile match = null; for (FileShadowingResolverExtensionBean extension : extensions) { IFileShadowingResolver resolver = extension.getHandler(); match = resolver.resolveType(virtualFile); if (match != null) { break; } } if(match == null) { return classes[0]; } for(PsiClass c : classes) { IFile f = FileUtil.toIFile(c.getContainingFile().getVirtualFile()); if(f.equals(match)) { return c; } } } return null; } @Nullable private static PsiElement resolveGosuType(@Nullable IType type, Project project, PsiElement context, @NotNull GlobalSearchScope scope) { PsiElement aClass = null; if (type instanceof IGosuClass && ((IGosuClass) type).isAnonymous()) { IGosuClass enclosingType = (IGosuClass) type.getEnclosingType(); if (enclosingType instanceof IBlockClass) { enclosingType = (IGosuClass) enclosingType.getEnclosingType(); } final PsiElement enclosingPsi = resolveTypeImpl(enclosingType, project, context, scope); aClass = resolveAnonymousGosuClass(enclosingPsi, type.getRelativeName()); } if (aClass == null && type != null) { aClass = resolveScratchpadInnerClass(type.getName(), context); } if (aClass == null && type != null && !(type instanceof IErrorType) && !(type instanceof INamespaceType) && !(type instanceof IGosuClass) && !(type instanceof IJavaType) && !(type instanceof IBlockType) && type instanceof IFileBasedType) { aClass = CustomPsiClassCache.instance().getPsiClass(type); } return aClass; } @Nullable private static PsiElement resolveAnonymousGosuClass(@Nullable PsiElement psiParent, @NotNull String internalName) { if (psiParent == null) { return null; } for (PsiElement child : psiParent.getChildren()) { if ((child instanceof IGosuAnonymousClassDefinition && internalName.equals(((IGosuAnonymousClassDefinition) child).getName())) || (child instanceof GosuBlockExpressionImpl && internalName.equals(((GosuBlockExpressionImpl) child).getParsedElement().getBlockGosuClass().getRelativeName()))) { return child; } else { PsiElement psiClass = resolveAnonymousGosuClass(child, internalName); if (psiClass != null) { return psiClass; } } } return null; } @Nullable private static PsiClass resolveScratchpadInnerClass(String fqn, @Nullable PsiElement context) { if (context != null) { final PsiFile file = context.getContainingFile(); if (file instanceof GosuScratchpadFileImpl) { return findNestedPsiClass(fqn, ((GosuScratchpadFileImpl) file).getPsiClass()); } } return null; } @Nullable private static PsiClass findNestedPsiClass(String fqn, @NotNull PsiClass enclosingClass) { String strEnclosingName = enclosingClass.getQualifiedName(); if (strEnclosingName != null && strEnclosingName.equals(fqn)) { return enclosingClass; } for (PsiClass inner : enclosingClass.getInnerClasses()) { PsiClass foundClass = findNestedPsiClass(fqn, inner); if (foundClass != null) { return foundClass; } } return null; } }