package com.intellij.javascript.flex.resolve; import com.intellij.lang.javascript.DialectOptionHolder; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.JSResolveHelper; import com.intellij.lang.javascript.index.JSIndexedRootProvider; import com.intellij.lang.javascript.index.JavaScriptIndex; import com.intellij.lang.javascript.psi.JSCommonTypeNames; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.resolve.JSClassResolver; import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.psi.stubs.JSQualifiedElementIndex; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.util.indexing.AdditionalIndexedRootsScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import static com.intellij.lang.javascript.psi.JSCommonTypeNames.*; /** * @author Konstantin.Ulitin */ public class ActionScriptClassResolver extends JSClassResolver { private static ActionScriptClassResolver INSTANCE = null; protected ActionScriptClassResolver() { } @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass") public static ActionScriptClassResolver getInstance() { if (INSTANCE == null) INSTANCE = new ActionScriptClassResolver(); return INSTANCE; } @Override public PsiElement findClassByQName(@NotNull String link, @NotNull PsiElement context) { return findClassByQNameStatic(link, context); } public static PsiElement findClassByQNameStatic(@NotNull String link, @NotNull PsiElement context) { return getInstance().findClassByQName(link, JavaScriptIndex.getInstance(context.getProject()), JSResolveUtil.getResolveScope(context), DialectOptionHolder.ECMA_4); } public static PsiElement findClassByQName(@NotNull final String link, final JavaScriptIndex index, final Module module) { GlobalSearchScope searchScope = JSInheritanceUtil.getEnforcedScope(); if (searchScope == null) { searchScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : GlobalSearchScope.allScope(index.getProject()); } return getInstance().findClassByQName(link, index, searchScope, DialectOptionHolder.ECMA_4); } @Nullable @Override public PsiElement findClassByQName(@NotNull String link, @NotNull GlobalSearchScope scope) { return findClassByQNameStatic(link, scope); } public static PsiElement findClassByQNameStatic(@NotNull final String link, @NotNull GlobalSearchScope scope) { return getInstance().findClassByQName(link, JavaScriptIndex.getInstance(scope.getProject()), scope, DialectOptionHolder.ECMA_4); } public static boolean isParentClass(JSClass clazz, String className) { return isParentClass(clazz, className, true); } public static boolean isParentClass(JSClass clazz, String className, boolean strict) { final PsiElement parentClass = findClassByQNameStatic(className, clazz.getResolveScope()); if (!(parentClass instanceof JSClass)) return false; return JSInheritanceUtil.isParentClass(clazz, (JSClass)parentClass, strict); } protected PsiElement doFindClassByQName(@NotNull String link, final JavaScriptIndex index, GlobalSearchScope searchScope, boolean allowFileLocalSymbols, @NotNull DialectOptionHolder dialect) { Project project = index.getProject(); boolean clazzShouldBeTakenFromOurLibrary = OBJECT_CLASS_NAME.equals(link) || "Arguments".equals(link); if (clazzShouldBeTakenFromOurLibrary && !(searchScope instanceof AdditionalIndexedRootsScope)) { // object from swf do not contain necessary members! searchScope = new AdditionalIndexedRootsScope(searchScope, JSIndexedRootProvider.class); } final Collection<JSQualifiedNamedElement> candidates = StubIndex.getElements(JSQualifiedElementIndex.KEY, link.hashCode(), project, searchScope, JSQualifiedNamedElement.class); ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); JSQualifiedNamedElement resultFromSourceContent = null; JSQualifiedNamedElement resultFromLibraries = null; long resultFromLibrariesTimestamp = 0; for (JSQualifiedNamedElement classCandidate : candidates) { if (!(classCandidate instanceof JSQualifiedNamedElement)) continue; if (JSResolveUtil.isConstructorFunction(classCandidate)) continue; JSQualifiedNamedElement clazz = classCandidate; if (link.equals(clazz.getQualifiedName())) { PsiFile file = clazz.getContainingFile(); if (!file.getLanguage().isKindOf(JavaScriptSupportLoader.ECMA_SCRIPT_L4)) continue; VirtualFile vFile = file.getVirtualFile(); if (clazzShouldBeTakenFromOurLibrary && !JavaScriptIndex.ECMASCRIPT_JS2.equals(vFile.getName()) // object from swf do not contain necessary members! ) { continue; } if (!allowFileLocalSymbols && JSResolveUtil.isFileLocalSymbol(clazz)) { continue; } if (projectFileIndex.isInSourceContent(vFile)) { // the absolute preference is for classes from sources resultFromSourceContent = clazz; continue; } // choose the right class in the same way as compiler does: with the latest timestamp in catalog.xml file if (resultFromLibraries == null) { resultFromLibraries = clazz; // do not initialize resultFromLibrariesTimestamp here, it is expensive and may be not required if only 1 candidate } else if (JSCommonTypeNames.VECTOR_CLASS_NAME.equals(link)) { if (clazz instanceof JSClass && resultFromLibraries instanceof JSClass && ((JSClass)clazz).getFunctions().length > ((JSClass)resultFromLibraries).getFunctions().length) { resultFromLibraries = clazz; } } else { if (resultFromLibrariesTimestamp == 0) { // was not initialized yet resultFromLibrariesTimestamp = getResolveResultTimestamp(resultFromLibraries); } final long classTimestamp = getResolveResultTimestamp(clazz); if (classTimestamp > resultFromLibrariesTimestamp) { resultFromLibraries = clazz; resultFromLibrariesTimestamp = classTimestamp; } } } } PsiElement result = resultFromSourceContent != null ? resultFromSourceContent : resultFromLibraries; if (result == null) { String className = link.substring(link.lastIndexOf('.') + 1); if (className.length() > 0 && !isBuiltInClassName(className) && (Character.isLetter(className.charAt(0)) || '_' == className.charAt(0))) { // TODO optimization, remove when packages will be properly handled result = findClassByQNameViaHelper(link, project, className, searchScope); } } return result; } private static boolean isBuiltInClassName(final String className) { return OBJECT_CLASS_NAME.equals(className) || BOOLEAN_CLASS_NAME.equals(className) || FUNCTION_CLASS_NAME.equals(className) || STRING_CLASS_NAME.equals(className); } @Nullable private static PsiElement findClassByQNameViaHelper(final String link, final Project project, final String className, final GlobalSearchScope scope) { for (JSResolveHelper helper : Extensions.getExtensions(JSResolveHelper.EP_NAME)) { PsiElement result = helper.findClassByQName(link, project, className, scope); if (result != null) return result; } return null; } }