android performance improvement: indexing of sdk resources
authorEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Mon, 30 Jan 2012 18:10:39 +0000 (22:10 +0400)
committerEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Tue, 31 Jan 2012 13:01:01 +0000 (17:01 +0400)
18 files changed:
plugins/android/src/META-INF/plugin.xml
plugins/android/src/org/jetbrains/android/AndroidIdIndex.java
plugins/android/src/org/jetbrains/android/AndroidResourcesLineMarkerProvider.java
plugins/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java [new file with mode: 0644]
plugins/android/src/org/jetbrains/android/AndroidXmlSchemaProvider.java
plugins/android/src/org/jetbrains/android/actions/CreateXmlResourceDialog.java
plugins/android/src/org/jetbrains/android/compiler/ResourceNamesValidityState.java
plugins/android/src/org/jetbrains/android/facet/AndroidFacet.java
plugins/android/src/org/jetbrains/android/facet/AndroidResourceFilesListener.java
plugins/android/src/org/jetbrains/android/resourceManagers/LocalResourceManager.java
plugins/android/src/org/jetbrains/android/resourceManagers/ResourceManager.java
plugins/android/src/org/jetbrains/android/resourceManagers/SystemResourceManager.java
plugins/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java
plugins/android/src/org/jetbrains/android/util/AndroidResourceUtil.java
plugins/android/src/org/jetbrains/android/util/ResourceEntry.java [moved from plugins/android/src/org/jetbrains/android/compiler/ResourceEntry.java with 88% similarity]
plugins/android/testData/dom/layout/layoutAttrs3.xml [new file with mode: 0644]
plugins/android/testSrc/org/jetbrains/android/AndroidTestCase.java
plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java

index bcbaf0fd30ca6e30cbd9a67249b0471e8db08691..e6e625de60e8f3c618176d118aad60fec8a2e19b 100644 (file)
                        serviceImplementation="org.jetbrains.android.maven.AndroidExternalApklibDependenciesManager"/>
     <xml.xmlExtension implementation="org.jetbrains.android.dom.AndroidXmlExtension" order="first"/>
     <fileBasedIndex implementation="org.jetbrains.android.AndroidIdIndex"/>
+    <fileBasedIndex implementation="org.jetbrains.android.AndroidValueResourcesIndex"/>
     <referencesSearch implementation="org.jetbrains.android.AndroidReferenceSearchExecutor"/>
     <projectService serviceInterface="org.jetbrains.android.compiler.AndroidDexCompilerConfiguration"
                     serviceImplementation="org.jetbrains.android.compiler.AndroidDexCompilerConfiguration"/>
index 0344676ae2290b0c9a6177d13e2fa5f77c877ed3..96e3738d55a387e33ae39d53ce6bca53e7833017 100644 (file)
@@ -30,7 +30,6 @@ import com.intellij.util.containers.HashMap;
 import com.intellij.util.indexing.*;
 import com.intellij.util.io.EnumeratorStringDescriptor;
 import com.intellij.util.io.KeyDescriptor;
-import org.jetbrains.android.resourceManagers.ResourceManager;
 import org.jetbrains.android.util.AndroidResourceUtil;
 import org.jetbrains.annotations.NotNull;
 
@@ -53,7 +52,7 @@ public class AndroidIdIndex extends ScalarIndexExtension<String> {
         if (parent == null || !parent.isDirectory()) {
           return false;
         }
-        final String resourceType = ResourceManager.getResourceTypeByDirName(parent.getName());
+        final String resourceType = AndroidResourceUtil.getResourceTypeByDirName(parent.getName());
         if (resourceType == null || !canContainIdDeclaration(resourceType)) {
           return false;
         }
index 2f2db3646235019bdf6716f91dd587eb5e684de4..0a29088483b38d11e71d26f9f338ee0b29c9b006 100644 (file)
@@ -171,7 +171,7 @@ public class AndroidResourcesLineMarkerProvider implements LineMarkerProvider {
     Collection<Resources> resourceFiles = resManager.getResourceElements();
     for (Resources res : resourceFiles) {
       for (String valueResourceType : ResourceManager.VALUE_RESOURCE_TYPES) {
-        for (ResourceElement valueResource : ResourceManager.getValueResources(valueResourceType, res)) {
+        for (ResourceElement valueResource : ResourceManager.getValueResourcesFromElement(valueResourceType, res)) {
           addResource(valueResourceType, valueResource, result);
         }
       }
@@ -197,7 +197,7 @@ public class AndroidResourcesLineMarkerProvider implements LineMarkerProvider {
       public void run() {
         List<VirtualFile> resourceSubdirs = resManager.getResourceSubdirs(null);
         for (VirtualFile dir : resourceSubdirs) {
-          String resType = ResourceManager.getResourceTypeByDirName(dir.getName());
+          String resType = AndroidResourceUtil.getResourceTypeByDirName(dir.getName());
           if (resType != null) {
             for (VirtualFile resourceFile : dir.getChildren()) {
               if (!resourceFile.isDirectory()) {
diff --git a/plugins/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java b/plugins/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java
new file mode 100644 (file)
index 0000000..8bc3234
--- /dev/null
@@ -0,0 +1,198 @@
+package org.jetbrains.android;
+
+import com.android.resources.ResourceType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.indexing.*;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import com.intellij.util.xml.NanoXmlUtil;
+import org.jetbrains.android.util.AndroidResourceUtil;
+import org.jetbrains.android.util.ResourceEntry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class AndroidValueResourcesIndex extends FileBasedIndexExtension<ResourceEntry, Set<ResourceEntry>> {
+  public static final ID<ResourceEntry, Set<ResourceEntry>> INDEX_ID = ID.create("android.value.resources.index");
+  
+  private final FileBasedIndex.InputFilter myInputFilter = new FileBasedIndex.InputFilter() {
+    public boolean acceptInput(final VirtualFile file) {
+      return (file.getFileSystem() == LocalFileSystem.getInstance() || file.getFileSystem() instanceof TempFileSystem) &&
+             file.getFileType() == StdFileTypes.XML;
+    }
+  };
+
+  private final DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent> myIndexer =
+    new DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent>() {
+    @NotNull
+    public Map<ResourceEntry, Set<ResourceEntry>> map(FileContent inputData) {
+      final Map<ResourceEntry, Set<ResourceEntry>> result = new HashMap<ResourceEntry, Set<ResourceEntry>>();
+      
+      NanoXmlUtil.parse(new ByteArrayInputStream(inputData.getContent()), new NanoXmlUtil.IXMLBuilderAdapter() {
+        private boolean mySeenResources = false;
+        private String myLastTypeAttr = null;
+        private String myLastNameAttr = null;
+        
+        @Override
+        public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr) throws Exception {
+          super.startElement(name, nsPrefix, nsURI, systemID, lineNr);
+          
+          if (!mySeenResources) {
+            if ("resources".equals(name)) {
+              mySeenResources = true;
+            }
+            else {
+              throw new NanoXmlUtil.ParserStoppedException();
+            }
+          }
+          myLastNameAttr = null;
+          myLastTypeAttr = null;
+        }
+
+        @Override
+        public void addAttribute(String key, String nsPrefix, String nsURI, String value, String type) throws Exception {
+          super.addAttribute(key, nsPrefix, nsURI, value, type);
+
+          if ("name".equals(key)) {
+            myLastNameAttr = value;
+          }
+          else if ("type".equals(key)) {
+            myLastTypeAttr = value;
+          }
+        }
+
+        @Override
+        public void elementAttributesProcessed(String name, String nsPrefix, String nsURI) throws Exception {
+          super.elementAttributesProcessed(name, nsPrefix, nsURI);
+          
+          if (myLastNameAttr != null && name != null) {
+            final String resType = "item".equals(name)
+                                   ? myLastTypeAttr
+                                   : AndroidResourceUtil.getResourceTypeByTagName(name);
+            if (resType != null && ResourceType.getEnum(resType) != null) {
+              final ResourceEntry entry = new ResourceEntry(resType, myLastNameAttr);
+              result.put(entry, Collections.<ResourceEntry>emptySet());
+
+              final ResourceEntry typeMarkerEntry = createTypeMarkerEntry(resType);
+              Set<ResourceEntry> set = result.get(typeMarkerEntry);
+
+              if (set == null) {
+                set = new HashSet<ResourceEntry>();
+                result.put(typeMarkerEntry, set);
+              }
+              set.add(entry);
+            }
+          }
+        }
+      });
+      
+      return result;
+    }
+  };
+
+  public static ResourceEntry createTypeMarkerEntry(String type) {
+    return new ResourceEntry(type, "TYPE_MARKER_RESOURCE");
+  }
+
+  private final KeyDescriptor<ResourceEntry> myKeyDescriptor = new KeyDescriptor<ResourceEntry>() {
+    @Override
+    public void save(DataOutput out, ResourceEntry value) throws IOException {
+      out.writeUTF(value.getType());
+      out.writeUTF(value.getName());
+    }
+
+    @Override
+    public ResourceEntry read(DataInput in) throws IOException {
+      final String resType = in.readUTF();
+      final String resName = in.readUTF();
+      return new ResourceEntry(resType, resName);
+    }
+
+    @Override
+    public int getHashCode(ResourceEntry value) {
+      return value.hashCode();
+    }
+
+    @Override
+    public boolean isEqual(ResourceEntry val1, ResourceEntry val2) {
+      return val1.equals(val2);
+    }
+  };
+  
+  private final DataExternalizer<Set<ResourceEntry>> myValueExternalizer = new DataExternalizer<Set<ResourceEntry>>() {
+    @Override
+    public void save(DataOutput out, Set<ResourceEntry> value) throws IOException {
+      out.writeInt(value.size());
+
+      for (ResourceEntry entry : value) {
+        myKeyDescriptor.save(out, entry);
+      }
+    }
+
+    @Nullable
+    @Override
+    public Set<ResourceEntry> read(DataInput in) throws IOException {
+      final int size = in.readInt();
+
+      if (size == 0) {
+        return Collections.emptySet();
+      }
+      final Set<ResourceEntry> result = new HashSet<ResourceEntry>(size);
+
+      for (int i = 0; i < size; i++) {
+        result.add(myKeyDescriptor.read(in));
+      }
+      return result;
+    }
+  };
+
+  @Override
+  public ID<ResourceEntry, Set<ResourceEntry>> getName() {
+    return INDEX_ID;
+  }
+
+  @Override
+  public DataIndexer<ResourceEntry, Set<ResourceEntry>, FileContent> getIndexer() {
+    return myIndexer;  
+  }
+
+  @Override
+  public KeyDescriptor<ResourceEntry> getKeyDescriptor() {
+    return myKeyDescriptor;
+  }
+
+  @Override
+  public DataExternalizer<Set<ResourceEntry>> getValueExternalizer() {
+    return myValueExternalizer;
+  }
+
+  @Override
+  public FileBasedIndex.InputFilter getInputFilter() {
+    return myInputFilter;
+  }
+
+  @Override
+  public boolean dependsOnFileContent() {
+    return true;
+  }
+
+  @Override
+  public int getVersion() {
+    return 0;
+  }
+}
index 9916c9cffec8742701120025c1fcd836a5941a9b..5652e9862fdb23a025601a1d3ef7bd6ef5a98ab2 100644 (file)
@@ -36,6 +36,7 @@ import gnu.trove.THashMap;
 import org.jetbrains.android.dom.manifest.ManifestDomFileDescription;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.resourceManagers.ResourceManager;
+import org.jetbrains.android.util.AndroidResourceUtil;
 import org.jetbrains.android.util.AndroidUtils;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -107,7 +108,7 @@ public class AndroidXmlSchemaProvider extends XmlSchemaProvider {
       return false;
     }
 
-    final String resType = ResourceManager.getResourceTypeByDirName(parent.getName());
+    final String resType = AndroidResourceUtil.getResourceTypeByDirName(parent.getName());
     if (resType == null) {
       return false;
     }
index dae344b92cedda4c5b97440668d1485709e93b02..8bc1055a1fd6469bd0211cee982fec67fc46703f 100644 (file)
@@ -141,7 +141,7 @@ public class CreateXmlResourceDialog extends DialogWrapper {
       return new ValidationInfo("specify module", myModuleCombo);
     }
     else if (!ResourceFolderType.VALUES.getName().equals(
-      ResourceManager.getResourceTypeByDirName(directoryName))) {
+      AndroidResourceUtil.getResourceTypeByDirName(directoryName))) {
       return new ValidationInfo("directory name is not appropriate for value resources");
     }
 
@@ -196,7 +196,7 @@ public class CreateXmlResourceDialog extends DialogWrapper {
       return new ValidationInfo(AndroidBundle.message("not.resource.file.error", FileUtil.toSystemDependentName(resFile.getPath())));
     }
 
-    for (ResourceElement element : ResourceManager.getValueResources(resourceType.getName(), resources)) {
+    for (ResourceElement element : ResourceManager.getValueResourcesFromElement(resourceType.getName(), resources)) {
       if (resourceName.equals(element.getName().getValue())) {
         return new ValidationInfo("resource '" + resourceName + "' already exists in " + FileUtil.toSystemDependentName(
           resFile.getPath()));
index e1aefdc3aa16e0a88922e3bf28133a6d9ae444f2..c1150a0226c635d84e316036a4b45f9d976ceb83 100644 (file)
@@ -8,6 +8,7 @@ import com.intellij.util.containers.HashSet;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.facet.AndroidRootUtil;
 import org.jetbrains.android.sdk.AndroidPlatform;
+import org.jetbrains.android.util.ResourceEntry;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.DataInput;
index 3e3b8a51ac3ba6a03df5a25c14c49f24adf6b172..371077fede8e4af0d26c50800a04314824b3e059 100644 (file)
@@ -80,6 +80,7 @@ import org.jetbrains.android.resourceManagers.SystemResourceManager;
 import org.jetbrains.android.sdk.*;
 import org.jetbrains.android.util.AndroidBundle;
 import org.jetbrains.android.util.AndroidUtils;
+import org.jetbrains.android.util.ResourceEntry;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
index 04208af7976a2aa70c5c591786fab37f0a498a6d..27d7809494f887002ce9695a03700852aae1c067 100644 (file)
@@ -32,6 +32,7 @@ import org.jetbrains.android.dom.manifest.Manifest;
 import org.jetbrains.android.fileTypes.AndroidIdlFileType;
 import org.jetbrains.android.fileTypes.AndroidRenderscriptFileType;
 import org.jetbrains.android.util.AndroidUtils;
+import org.jetbrains.android.util.ResourceEntry;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
index 5369c0d85a2194f8b7f393130fa142a693cd65fa..91ae8a93420cebe63363db38410bb8bf9a113956 100644 (file)
@@ -20,7 +20,6 @@ import com.android.AndroidConstants;
 import com.intellij.CommonBundle;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileTypes.StdFileTypes;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
@@ -101,21 +100,7 @@ public class LocalResourceManager extends ResourceManager {
   }
 
   public List<Resources> getResourceElements() {
-    return getResourceElements(getAllResourceFiles());
-  }
-
-  @NotNull
-  private Set<VirtualFile> getAllResourceFiles() {
-    final Set<VirtualFile> files = new HashSet<VirtualFile>();
-
-    for (VirtualFile valueResourceDir : getResourceSubdirs("values")) {
-      for (VirtualFile valueResourceFile : valueResourceDir.getChildren()) {
-        if (!valueResourceFile.isDirectory() && valueResourceFile.getFileType().equals(StdFileTypes.XML)) {
-          files.add(valueResourceFile);
-        }
-      }
-    }
-    return files;
+    return getResourceElements(null);
   }
 
   @NotNull
@@ -141,7 +126,7 @@ public class LocalResourceManager extends ResourceManager {
 
   @NotNull
   public List<ResourceElement> getValueResources(@NotNull final String resourceType) {
-    return getValueResources(resourceType, getAllResourceFiles());
+    return getValueResources(resourceType, null);
   }
 
   private static void collectResourceDirs(Module module, Set<VirtualFile> result, Set<Module> visited) {
index d0bee9a740659a3f8f9f2dcd0377df27726433f0..b720cce15aa41e29c9b66456da2b5dc407145000 100644 (file)
@@ -17,6 +17,7 @@ package org.jetbrains.android.resourceManagers;
 
 import com.android.sdklib.SdkConstants;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileTypes.StdFileTypes;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleUtil;
 import com.intellij.openapi.project.Project;
@@ -29,13 +30,14 @@ import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
 import com.intellij.psi.xml.XmlTag;
 import com.intellij.util.ArrayUtil;
-import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.HashSet;
 import com.intellij.util.xml.DomElement;
 import org.jetbrains.android.dom.attrs.AttributeDefinitions;
 import org.jetbrains.android.dom.resources.Item;
 import org.jetbrains.android.dom.resources.ResourceElement;
 import org.jetbrains.android.dom.resources.Resources;
 import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.android.util.AndroidResourceUtil;
 import org.jetbrains.android.util.AndroidUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -93,33 +95,9 @@ public abstract class ResourceManager {
     return VirtualFile.EMPTY_ARRAY;
   }
 
-  @Nullable
-  public static String getResourceTypeByDirName(@NotNull String name) {
-    int index = name.indexOf('-');
-    String type = index >= 0 ? name.substring(0, index) : name;
-    return ArrayUtil.find(FILE_RESOURCE_TYPES, type) >= 0 ? type : null;
-  }
-
   @NotNull
   public List<VirtualFile> getResourceSubdirs(@Nullable String resourceType) {
-    List<VirtualFile> dirs = new ArrayList<VirtualFile>();
-    if (ArrayUtil.find(FILE_RESOURCE_TYPES, resourceType) < 0 && resourceType != null) {
-      return dirs;
-    }
-    VirtualFile[] resourcesDirs = getAllResourceDirs();
-    for (VirtualFile resourcesDir : resourcesDirs) {
-      if (resourcesDir == null) return dirs;
-      if (resourceType == null) {
-        ContainerUtil.addAll(dirs, resourcesDir.getChildren());
-      }
-      else {
-        for (VirtualFile child : resourcesDir.getChildren()) {
-          String type = getResourceTypeByDirName(child.getName());
-          if (resourceType.equals(type)) dirs.add(child);
-        }
-      }
-    }
-    return dirs;
+    return AndroidResourceUtil.getResourceSubdirs(resourceType, getAllResourceDirs());
   }
 
   @NotNull
@@ -193,21 +171,37 @@ public abstract class ResourceManager {
     return findResourceFiles(resType, null, true);
   }
 
-  protected List<Resources> getResourceElements(@NotNull Set<VirtualFile> files) {
+  protected List<Resources> getResourceElements(@Nullable Set<VirtualFile> files) {
     return getRootDomElements(Resources.class, files);
   }
 
   private <T extends DomElement> List<T> getRootDomElements(@NotNull Class<T> elementType,
-                                                            @NotNull Collection<VirtualFile> files) {
+                                                            @Nullable Set<VirtualFile> files) {
     final List<T> result = new ArrayList<T>();
-    for (VirtualFile file : files) {
-      T element = AndroidUtils.loadDomElement(myModule, file, elementType);
-      if (element != null) result.add(element);
+    for (VirtualFile file : getAllResourceFiles()) {
+      if ((files == null || files.contains(file)) && file.isValid()) {
+        T element = AndroidUtils.loadDomElement(myModule, file, elementType);
+        if (element != null) result.add(element);
+      }
     }
     return result;
   }
 
-  protected List<ResourceElement> getValueResources(final String resourceType, Set<VirtualFile> files) {
+  @NotNull
+  private Set<VirtualFile> getAllResourceFiles() {
+    final Set<VirtualFile> files = new HashSet<VirtualFile>();
+
+    for (VirtualFile valueResourceDir : getResourceSubdirs("values")) {
+      for (VirtualFile valueResourceFile : valueResourceDir.getChildren()) {
+        if (!valueResourceFile.isDirectory() && valueResourceFile.getFileType().equals(StdFileTypes.XML)) {
+          files.add(valueResourceFile);
+        }
+      }
+    }
+    return files;
+  }
+
+  protected List<ResourceElement> getValueResources(@NotNull final String resourceType, @Nullable Set<VirtualFile> files) {
     final List<ResourceElement> result = new ArrayList<ResourceElement>();
     Collection<Resources> resourceFiles = getResourceElements(files);
     for (final Resources resources : resourceFiles) {
@@ -217,7 +211,7 @@ public abstract class ResourceManager {
           if (!resources.isValid() || myModule.isDisposed() || myModule.getProject().isDisposed()) {
             return;
           }
-          result.addAll(getValueResources(resourceType, resources));
+          result.addAll(getValueResourcesFromElement(resourceType, resources));
         }
       });
     }
@@ -245,7 +239,7 @@ public abstract class ResourceManager {
         if (possibleResDir == null || !isResourceDir(possibleResDir.getVirtualFile())) {
           return null;
         }
-        String type = getResourceTypeByDirName(dir.getName());
+        String type = AndroidResourceUtil.getResourceTypeByDirName(dir.getName());
         if (type == null) return null;
         return isCorrectFileName(type, file.getName()) ? type : null;
       }
@@ -270,7 +264,7 @@ public abstract class ResourceManager {
   public abstract Collection<String> getValueResourceNames(@NotNull final String resourceType);
 
   @NotNull
-  public static List<ResourceElement> getValueResources(@NotNull String resourceType, Resources resources) {
+  public static List<ResourceElement> getValueResourcesFromElement(@NotNull String resourceType, Resources resources) {
     List<ResourceElement> result = new ArrayList<ResourceElement>();
     if (resourceType.equals("string")) {
       result.addAll(resources.getStrings());
index 83604208d580d51e9942ef7ca48b098c726bdc76..43147141ebf56f391214f1331df7e9f701f04280 100644 (file)
  */
 package org.jetbrains.android.resourceManagers;
 
-import com.android.resources.ResourceFolderType;
 import com.android.resources.ResourceType;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkConstants;
-import com.intellij.lang.injection.InjectedLanguageManager;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileTypes.StdFileTypes;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.*;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.SmartPointerManager;
+import com.intellij.psi.SmartPsiElementPointer;
+import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.xml.*;
 import com.intellij.util.containers.HashMap;
 import com.intellij.util.containers.HashSet;
+import com.intellij.util.indexing.FileBasedIndex;
 import com.intellij.util.xml.ConvertContext;
 import org.jetbrains.android.AndroidIdIndex;
+import org.jetbrains.android.AndroidValueResourcesIndex;
 import org.jetbrains.android.dom.attrs.AttributeDefinitions;
 import org.jetbrains.android.dom.resources.ResourceElement;
-import org.jetbrains.android.dom.resources.Resources;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.sdk.AndroidPlatform;
 import org.jetbrains.android.sdk.AndroidTargetData;
 import org.jetbrains.android.util.AndroidResourceUtil;
-import org.jetbrains.android.util.AndroidUtils;
+import org.jetbrains.android.util.ResourceEntry;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -53,8 +54,6 @@ public class SystemResourceManager extends ResourceManager {
   
   private volatile Map<String, List<SmartPsiElementPointer<? extends PsiElement>>> myIdMap;
   
-  private volatile Map<ResourceType, Map<String, Set<VirtualFile>>> myValueResourcesMap;
-  
   private final AndroidPlatform myPlatform;
 
   public SystemResourceManager(@NotNull Module module, @NotNull AndroidPlatform androidPlatform) {
@@ -83,8 +82,17 @@ public class SystemResourceManager extends ResourceManager {
       LOG.error("Unknown resource type " + resourceType);
       return Collections.emptyList();
     }
-    final Map<String, Set<VirtualFile>> map = getValueResourcesMap().get(type);
-    return map != null ? map.keySet() : Collections.<String>emptyList();
+    final Set<String> result = new HashSet<String>();
+    final ResourceEntry typeMarkerEntry = AndroidValueResourcesIndex.createTypeMarkerEntry(resourceType);
+    final List<Set<ResourceEntry>> values = FileBasedIndex.getInstance()
+      .getValues(AndroidValueResourcesIndex.INDEX_ID, typeMarkerEntry, GlobalSearchScope.moduleWithLibrariesScope(myModule));
+
+    for (Set<ResourceEntry> entrySet : values) {
+      for (ResourceEntry entry : entrySet) {
+        result.add(entry.getName());
+      }
+    }
+    return result;
   }
 
   @Nullable
@@ -96,7 +104,7 @@ public class SystemResourceManager extends ResourceManager {
   @Nullable
   public synchronized AttributeDefinitions getAttributeDefinitions() {
     final AndroidTargetData targetData = myPlatform.getSdk().getTargetData(myPlatform.getTarget());
-    return targetData.getAttrDefs(myModule.getProject());
+    return targetData != null ? targetData.getAttrDefs(myModule.getProject()) : null;
   }
 
   @Nullable
@@ -108,68 +116,6 @@ public class SystemResourceManager extends ResourceManager {
     return doFindIdDeclarations(id, true);
   }
 
-  @NotNull
-  private synchronized Map<ResourceType, Map<String, Set<VirtualFile>>> getValueResourcesMap() {
-    if (myValueResourcesMap == null) {
-      myValueResourcesMap = new HashMap<ResourceType, Map<String, Set<VirtualFile>>>();
-      
-      for (VirtualFile valueResourceDir : getResourceSubdirs(ResourceFolderType.VALUES.getName())) {
-        for (final VirtualFile valueResourceFile : valueResourceDir.getChildren()) {
-          if (!valueResourceFile.isDirectory() && valueResourceFile.getFileType().equals(StdFileTypes.XML)) {
-            ApplicationManager.getApplication().runReadAction(new Runnable() {
-              @Override
-              public void run() {
-                fillValueResourcesMap(valueResourceFile);
-              }
-            });
-          }
-        }
-      }
-    }
-    return myValueResourcesMap;
-  }
-
-  private void fillValueResourcesMap(@NotNull VirtualFile valueResourceFile) {
-    final Resources roots = AndroidUtils.loadDomElement(myModule, valueResourceFile, Resources.class);
-    if (roots == null) {
-      return;
-    }
-    
-    for (ResourceType resType : ResourceType.values()) {
-      Map<String, Set<VirtualFile>> map = myValueResourcesMap.get(resType);
-
-      if (map == null) {
-        map = new HashMap<String, Set<VirtualFile>>();
-        myValueResourcesMap.put(resType, map);
-      }
-
-      for (ResourceElement element : ResourceManager.getValueResources(resType.getName(), roots)) {
-        final String name = element.getName().getValue();
-
-        if (name != null) {
-          Set<VirtualFile> fileSet = map.get(name);
-
-          if (fileSet == null) {
-            fileSet = new HashSet<VirtualFile>();
-            map.put(name, fileSet);
-          }
-          fileSet.add(valueResourceFile);
-        }
-      }
-    }
-
-    PsiManager.getInstance(myModule.getProject()).dropResolveCaches();
-    final XmlElement element = roots.getXmlElement();
-    
-    if (element != null) {
-      final PsiFile file = element.getContainingFile();
-      
-      if (file != null) {
-        InjectedLanguageManager.getInstance(myModule.getProject()).dropFileCaches(file);
-      }
-    }
-  }
-
   private List<PsiElement> doFindIdDeclarations(@NotNull String id, boolean recreateMapIfCannotResolve) {
     final List<SmartPsiElementPointer<? extends PsiElement>> pointers = myIdMap.get(id);
 
@@ -210,17 +156,16 @@ public class SystemResourceManager extends ResourceManager {
       LOG.error("Unknown resource type " + resourceType);
       return Collections.emptyList();
     }
-    
-    final Map<String, Set<VirtualFile>> map = getValueResourcesMap().get(type);
-    if (map == null) {
-      return Collections.emptyList();
-    }
 
-    final Set<VirtualFile> fileSet = map.get(resourceName);
-    if (fileSet == null) {
+    final Collection<VirtualFile> files = FileBasedIndex.getInstance()
+      .getContainingFiles(AndroidValueResourcesIndex.INDEX_ID, new ResourceEntry(resourceType, resourceName),
+                          GlobalSearchScope.moduleWithLibrariesScope(
+                            myModule));
+
+    if (files.size() == 0) {
       return Collections.emptyList();
     }
-
+    final Set<VirtualFile> fileSet = new HashSet<VirtualFile>(files);
     final List<ResourceElement> result = new ArrayList<ResourceElement>();
     
     for (ResourceElement element : getValueResources(resourceType, fileSet)) {
index 7f422a5134b14fd527ddd0f03152cce319b5a4cc..766e05545306205acabe1595ab4ece018796dec8 100644 (file)
@@ -98,6 +98,16 @@ public class AndroidSdkUtils {
     if (sdkDir != null) {
       addJavaDocAndSources(result, sdkDir);
     }
+
+    final String resFolderPath = target.getPath(IAndroidTarget.RESOURCES);
+
+    if (resFolderPath != null) {
+      final VirtualFile resFolder = LocalFileSystem.getInstance().findFileByPath(resFolderPath);
+
+      if (resFolder != null) {
+        result.add(new OrderRoot(resFolder, OrderRootType.CLASSES));
+      }
+    }
     return result;
   }
 
index 4d95bb5dc5b132ee29b5972f73ae51be49effd42..8fe65fbb034b5664475b95d1bdd8bcbd544a6590 100644 (file)
@@ -30,7 +30,9 @@ import com.intellij.psi.xml.XmlAttribute;
 import com.intellij.psi.xml.XmlAttributeValue;
 import com.intellij.psi.xml.XmlFile;
 import com.intellij.psi.xml.XmlTag;
+import com.intellij.util.ArrayUtil;
 import com.intellij.util.Processor;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.HashSet;
 import com.intellij.util.indexing.FileBasedIndex;
 import org.jetbrains.android.AndroidIdIndex;
@@ -217,24 +219,34 @@ public class AndroidResourceUtil {
     LocalResourceManager manager = facet.getLocalResourceManager();
     String fileResType = manager.getFileResourceType(tag.getContainingFile());
     if ("values".equals(fileResType)) {
-      String resClassName = tag.getName();
-      if (resClassName.equals("item")) {
-        resClassName = tag.getAttributeValue("type", null);
-      }
-      else if (resClassName.equals("declare-styleable")) {
-        resClassName = "styleable";
-      }
-      else if (resClassName.endsWith("-array")) {
-        resClassName = "array";
-      }
-      if (resClassName != null) {
-        String resourceName = tag.getAttributeValue("name");
-        return resourceName != null ? resClassName : null;
-      }
+      return getResourceTypeByValueResourceTag(tag);
+    }
+    return null;
+  }
+
+  @Nullable
+  public static String getResourceTypeByValueResourceTag(XmlTag tag) {
+    String resClassName = tag.getName();
+    resClassName = resClassName.equals("item")
+                   ? tag.getAttributeValue("type", null)
+                   : getResourceTypeByTagName(resClassName);
+    if (resClassName != null) {
+      final String resourceName = tag.getAttributeValue("name");
+      return resourceName != null ? resClassName : null;
     }
     return null;
   }
 
+  public static String getResourceTypeByTagName(@NotNull String tagName) {
+    if (tagName.equals("declare-styleable")) {
+      tagName = "styleable";
+    }
+    else if (tagName.endsWith("-array")) {
+      tagName = "array";
+    }
+    return tagName;
+  }
+
   @Nullable
   public static String getResourceClassName(@NotNull PsiField field) {
     PsiClass resourceClass = field.getContainingClass();
@@ -435,4 +447,32 @@ public class AndroidResourceUtil {
     }
     throw new IllegalArgumentException("Incorrect resource type");
   }
+
+  @Nullable
+  public static String getResourceTypeByDirName(@NotNull String name) {
+    int index = name.indexOf('-');
+    String type = index >= 0 ? name.substring(0, index) : name;
+    return ArrayUtil.find(ResourceManager.FILE_RESOURCE_TYPES, type) >= 0 ? type : null;
+  }
+
+  @NotNull
+  public static List<VirtualFile> getResourceSubdirs(@Nullable String resourceType, @NotNull VirtualFile[] resourceDirs) {
+    List<VirtualFile> dirs = new ArrayList<VirtualFile>();
+    if (ArrayUtil.find(ResourceManager.FILE_RESOURCE_TYPES, resourceType) < 0 && resourceType != null) {
+      return dirs;
+    }
+    for (VirtualFile resourcesDir : resourceDirs) {
+      if (resourcesDir == null) return dirs;
+      if (resourceType == null) {
+        ContainerUtil.addAll(dirs, resourcesDir.getChildren());
+      }
+      else {
+        for (VirtualFile child : resourcesDir.getChildren()) {
+          String type = getResourceTypeByDirName(child.getName());
+          if (resourceType.equals(type)) dirs.add(child);
+        }
+      }
+    }
+    return dirs;
+  }
 }
similarity index 88%
rename from plugins/android/src/org/jetbrains/android/compiler/ResourceEntry.java
rename to plugins/android/src/org/jetbrains/android/util/ResourceEntry.java
index 868e523eb3f21b0f0389ffecb3bd1cebd5c4a9f9..c9fbe72541fa226d34caa7ae966e99b4f633cb91 100644 (file)
@@ -1,4 +1,4 @@
-package org.jetbrains.android.compiler;
+package org.jetbrains.android.util;
 
 import org.jetbrains.annotations.NotNull;
 
@@ -9,7 +9,7 @@ public class ResourceEntry {
   private final String myType;
   private final String myName;
 
-  ResourceEntry(@NotNull String type, @NotNull String name) {
+  public ResourceEntry(@NotNull String type, @NotNull String name) {
     myType = type;
     myName = name;
   }
diff --git a/plugins/android/testData/dom/layout/layoutAttrs3.xml b/plugins/android/testData/dom/layout/layoutAttrs3.xml
new file mode 100644 (file)
index 0000000..d3f552d
--- /dev/null
@@ -0,0 +1,3 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              style="<error>@android:style/unknownStyle</error>">
+</LinearLayout>
\ No newline at end of file
index 27721ed74b6084f6ba69ec002af1ce41fe1de244..966daab2e5cdc0b8d7885cc6a535219e24a16bcb 100644 (file)
@@ -29,6 +29,7 @@ import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.roots.ModuleRootManager;
 import com.intellij.openapi.roots.OrderRootType;
 import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
 import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
@@ -151,6 +152,9 @@ public abstract class AndroidTestCase extends JavaCodeInsightFixtureTestCase {
     VirtualFile androidJar = JarFileSystem.getInstance().findFileByPath(androidJarPath);
     sdkModificator.addRoot(androidJar, OrderRootType.CLASSES);
 
+    VirtualFile resFolder = LocalFileSystem.getInstance().findFileByPath(sdkPath + "/platforms/android-1.5/data/res");
+    sdkModificator.addRoot(resFolder, OrderRootType.CLASSES);
+
     AndroidSdkAdditionalData data = new AndroidSdkAdditionalData(sdk);
     AndroidSdk sdkObject = AndroidSdk.parse(sdkPath, new EmptySdkLog());
     data.setBuildTarget(sdkObject.findTargetByName("Android 1.5"));
index bc2c6d48a91106c120a7c991dd87d5b9c453037f..ebf4b0d9ac74047cbf39eeef3fa79882db17639e 100644 (file)
@@ -83,6 +83,10 @@ public class AndroidLayoutDomTest extends AndroidDomTest {
     doTestHighlighting("layoutAttrs2.xml");
   }
 
+  public void testCheckLayoutAttrs3() throws Throwable {
+    doTestHighlighting("layoutAttrs3.xml");
+  }
+
   public void testUnknownAttribute() throws Throwable {
     doTestHighlighting("hl1.xml");
   }