From db7d37f111530ec477b0ece934a0d2f4fd845c04 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Wed, 18 Jul 2012 21:54:37 +0400 Subject: [PATCH] use psi pointers in class maps, store class maps only for lib classes --- .../jetbrains/android/facet/AndroidFacet.java | 93 ++++++++++++++----- plugins/android/testData/dom/layout/ctn0.xml | 3 + .../android/dom/AndroidLayoutDomTest.java | 44 +++++++++ 3 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 plugins/android/testData/dom/layout/ctn0.xml diff --git a/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java b/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java index 8d5a4150966d..962a12211589 100644 --- a/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java +++ b/plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java @@ -61,10 +61,7 @@ import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.psi.search.searches.ClassInheritorsSearch; @@ -108,7 +105,8 @@ public class AndroidFacet extends Facet { private SystemResourceManager mySystemResourceManager; private LocalResourceManager myLocalResourceManager; - private final Map> myClassMaps = new HashMap>(); + private final Map>> myClassMaps = + new HashMap>>(); private final Object myClassMapLock = new Object(); @@ -215,6 +213,7 @@ public class AndroidFacet extends Facet { myAvdManager = null; myLocalResourceManager = null; mySystemResourceManager = null; + myClassMaps.clear(); } // can be invoked only from dispatch thread! @@ -483,8 +482,9 @@ public class AndroidFacet extends Facet { if (isDisposed()) { return; } + final ModuleRootManager rootManager = ModuleRootManager.getInstance(getModule()); - final Sdk newSdk = ModuleRootManager.getInstance(getModule()).getSdk(); + final Sdk newSdk = rootManager.getSdk(); if (newSdk != null && newSdk.getSdkType() instanceof AndroidSdkType && !newSdk.equals(myPrevSdk)) { androidPlatformChanged(); @@ -769,43 +769,83 @@ public class AndroidFacet extends Facet { return (AndroidFacetType)FacetTypeRegistry.getInstance().findFacetType(ID); } + // todo: correctly support classes from external non-platform jars @NotNull public Map getClassMap(@NotNull String className, @NotNull ClassMapConstructor constructor) { synchronized (myClassMapLock) { - Map classMap = getInitialClassMap(className, constructor); - Map result = new HashMap(); - for (Iterator it = classMap.keySet().iterator(); it.hasNext();) { - String key = it.next(); - PsiClass value = classMap.get(key); - String[] tagNames = constructor.getTagNamesByClass(value); - if (ArrayUtil.find(tagNames, key) < 0) { - it.remove(); + Map> classMap = getInitialClassMap(className, constructor, false); + final Map result = new HashMap(); + boolean shouldRebuildInitialMap = false; + + for (final String key : classMap.keySet()) { + final SmartPsiElementPointer pointer = classMap.get(key); + + if (!isUpToDate(pointer, key, constructor)) { + shouldRebuildInitialMap = true; + break; } - else { - result.put(key, value); + final PsiClass aClass = pointer.getElement(); + + if (aClass != null) { + result.put(key, aClass); } } - Project project = getModule().getProject(); - fillMap(className, constructor, ProjectScope.getProjectScope(project), result); + + if (shouldRebuildInitialMap) { + result.clear(); + classMap = getInitialClassMap(className, constructor, true); + + for (final String key : classMap.keySet()) { + final SmartPsiElementPointer pointer = classMap.get(key); + final PsiClass aClass = pointer.getElement(); + + if (aClass != null) { + result.put(key, aClass); + } + } + } + final Project project = getModule().getProject(); + fillMap(className, constructor, ProjectScope.getProjectScope(project), result, false); return result; } } + private static boolean isUpToDate(SmartPsiElementPointer pointer, String tagName, ClassMapConstructor constructor) { + final PsiClass aClass = pointer.getElement(); + if (aClass == null) { + return false; + } + final String[] tagNames = constructor.getTagNamesByClass(aClass); + return ArrayUtil.find(tagNames, tagName) >= 0; + } + @NotNull - private Map getInitialClassMap(@NotNull String className, @NotNull ClassMapConstructor constructor) { - Map viewClassMap = myClassMaps.get(className); - if (viewClassMap != null) return viewClassMap; - viewClassMap = new HashMap(); - if (fillMap(className, constructor, getModule().getModuleWithDependenciesAndLibrariesScope(true), viewClassMap)) { + private Map> getInitialClassMap(@NotNull String className, + @NotNull ClassMapConstructor constructor, + boolean forceRebuild) { + Map> viewClassMap = myClassMaps.get(className); + if (viewClassMap != null && !forceRebuild) return viewClassMap; + final HashMap map = new HashMap(); + + if (fillMap(className, constructor, getModule().getModuleWithDependenciesAndLibrariesScope(true), map, true)) { + viewClassMap = new HashMap>(map.size()); + final SmartPointerManager manager = SmartPointerManager.getInstance(getModule().getProject()); + + for (Map.Entry entry : map.entrySet()) { + viewClassMap.put(entry.getKey(), manager.createSmartPsiElementPointer(entry.getValue())); + } myClassMaps.put(className, viewClassMap); } - return viewClassMap; + return viewClassMap != null + ? viewClassMap + : Collections.>emptyMap(); } private boolean fillMap(@NotNull final String className, @NotNull final ClassMapConstructor constructor, GlobalSearchScope scope, - final Map map) { + final Map map, + final boolean libClassesOnly) { final JavaPsiFacade facade = JavaPsiFacade.getInstance(getModule().getProject()); final PsiClass baseClass = ApplicationManager.getApplication().runReadAction(new Computable() { @Nullable @@ -821,6 +861,9 @@ public class AndroidFacet extends Facet { try { ClassInheritorsSearch.search(baseClass, scope, true).forEach(new Processor() { public boolean process(PsiClass c) { + if (libClassesOnly && c.getManager().isInProject(c)) { + return true; + } String[] tagNames = constructor.getTagNamesByClass(c); for (String tagName : tagNames) { map.put(tagName, c); diff --git a/plugins/android/testData/dom/layout/ctn0.xml b/plugins/android/testData/dom/layout/ctn0.xml new file mode 100644 index 000000000000..8df582f5ed92 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn0.xml @@ -0,0 +1,3 @@ + + < + \ No newline at end of file diff --git a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java index ab30be88ba6a..9b6c2440d6e3 100644 --- a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java @@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; @@ -100,6 +101,49 @@ public class AndroidLayoutDomTest extends AndroidDomTest { toTestCompletion("ctn.xml", "ctn_after.xml"); } + @SuppressWarnings("ConstantConditions") + public void testCustomTagCompletion0() throws Throwable { + final VirtualFile labelViewJava = copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + + VirtualFile lf1 = myFixture.copyFileToProject(testFolder + '/' + "ctn0.xml", "res/layout/layout1.xml"); + myFixture.configureFromExistingVirtualFile(lf1); + myFixture.complete(CompletionType.BASIC); + List variants = myFixture.getLookupElementStrings(); + assertTrue(variants.contains("p1.p2.LabelView")); + + final PsiFile psiLabelViewFile = PsiManager.getInstance(getProject()).findFile(labelViewJava); + assertInstanceOf(psiLabelViewFile, PsiJavaFile.class); + final PsiClass labelViewClass = ((PsiJavaFile)psiLabelViewFile).getClasses()[0]; + assertNotNull(labelViewClass); + myFixture.renameElement(labelViewClass, "LabelView1"); + + VirtualFile lf2 = myFixture.copyFileToProject(testFolder + '/' + "ctn0.xml", "res/layout/layout2.xml"); + myFixture.configureFromExistingVirtualFile(lf2); + myFixture.complete(CompletionType.BASIC); + variants = myFixture.getLookupElementStrings(); + assertFalse(variants.contains("p1.p2.LabelView")); + assertTrue(variants.contains("p1.p2.LabelView1")); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + labelViewJava.delete(null); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + + VirtualFile lf3 = myFixture.copyFileToProject(testFolder + '/' + "ctn0.xml", "res/layout/layout3.xml"); + myFixture.configureFromExistingVirtualFile(lf3); + myFixture.complete(CompletionType.BASIC); + variants = myFixture.getLookupElementStrings(); + assertFalse(variants.contains("p1.p2.LabelView")); + assertFalse(variants.contains("p1.p2.LabelView1")); + } + public void testCustomTagCompletion1() throws Throwable { copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); copyFileToProject("LabelView1.java", "src/p1/p2/LabelView1.java");