OSDN Git Service

ADT Resource Repo stores IDs and Called Once
authorJosiah Gaskin <josiahgaskin@google.com>
Mon, 18 Jul 2011 23:54:43 +0000 (16:54 -0700)
committerJosiah Gaskin <josiahgaskin@google.com>
Mon, 1 Aug 2011 23:47:28 +0000 (16:47 -0700)
This change makes ADT parse out @+id declarations from layout and
menu files and store them in its ResourceRepository. This eliminates
the need to merge in IDs from the generated R.java in order to have
a complete repository of resources.

In order to do this, a new type of ResourceFile is created:
IdGeneratingResourceFile which is used for xml files in menu/ and layout/.
Also includes an IdResourceParser which finds all the declared IDs in an XML.

This change also includes a change to the ResourceMananger to process
raw deltas that can be provided either from the PreCompilerBuilder or from
the GlobalProjectManager. This is to avoid duplicate deltas being passed
to ResourceManager when autobuilding.

Change-Id: I1c705a5f5a01ce7daa3eb76f14bd6cb9e11b5248

common/src/com/android/resources/FolderTypeRelationship.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java [new file with mode: 0644]
ide_common/src/com/android/ide/common/resources/IdResourceParser.java [new file with mode: 0644]
ide_common/src/com/android/ide/common/resources/ResourceFolder.java

index 34961a3..61a6d85 100644 (file)
@@ -53,7 +53,9 @@ public final class FolderTypeRelationship {
         add(ResourceType.INTEGER, ResourceFolderType.VALUES);
         add(ResourceType.INTERPOLATOR, ResourceFolderType.INTERPOLATOR);
         add(ResourceType.LAYOUT, ResourceFolderType.LAYOUT);
+        add(ResourceType.ID, ResourceFolderType.LAYOUT);
         add(ResourceType.MENU, ResourceFolderType.MENU);
+        add(ResourceType.ID, ResourceFolderType.MENU);
         add(ResourceType.MIPMAP, ResourceFolderType.MIPMAP);
         add(ResourceType.PLURALS, ResourceFolderType.VALUES);
         add(ResourceType.PUBLIC, ResourceFolderType.VALUES);
index 672995f..0163401 100644 (file)
@@ -30,6 +30,7 @@ import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
 import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig;
 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.ide.eclipse.adt.io.IFileWrapper;
@@ -253,6 +254,8 @@ public class PreCompilerBuilder extends BaseBuilder {
                 } else {
                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
                     delta.accept(dv);
+                    // Notify the ResourceManager:
+                    ResourceManager.getInstance().processDelta(delta);
 
                     // record the state
                     mMustCompileResources |= dv.getCompileResources();
index 172f471..c917d1c 100644 (file)
@@ -17,8 +17,8 @@
 package com.android.ide.eclipse.adt.internal.resources.manager;
 
 import com.android.ide.common.resources.IntArrayWrapper;
-import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
@@ -32,6 +32,7 @@ import org.eclipse.core.resources.IMarkerDelta;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
@@ -78,7 +79,9 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi
      * @see IFileListener#fileChanged
      */
     public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
-        if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) {
+        // Don't execute if we're autobuilding, let the precompiler take care of the delta
+        if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)
+                && ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding() == false) {
             loadAndParseRClass(file.getProject());
         }
     }
index cc615ec..f2e6485 100644 (file)
@@ -131,6 +131,14 @@ public final class GlobalProjectMonitor {
     }
 
     /**
+     * Interface for a listener that gets passed the raw delta without processing.
+     *
+     */
+    public interface IRawDeltaListener {
+        public void visitDelta(IResourceDelta delta);
+    }
+
+    /**
      * Base listener bundle to associate a listener to an event mask.
      */
     private static class ListenerBundle {
@@ -176,6 +184,9 @@ public final class GlobalProjectMonitor {
     private final ArrayList<IResourceEventListener> mEventListeners =
         new ArrayList<IResourceEventListener>();
 
+    private final ArrayList<IRawDeltaListener> mRawDeltaListeners =
+        new ArrayList<IRawDeltaListener>();
+
     private IWorkspace mWorkspace;
 
     /**
@@ -184,6 +195,11 @@ public final class GlobalProjectMonitor {
     private final class DeltaVisitor implements IResourceDeltaVisitor {
 
         public boolean visit(IResourceDelta delta) {
+            // notify the raw delta listeners
+            for (IRawDeltaListener listener : mRawDeltaListeners) {
+                listener.visitDelta(delta);
+            }
+            // Find the other resource listeners to notify
             IResource r = delta.getResource();
             int type = r.getType();
             if (type == IResource.FILE) {
@@ -395,7 +411,23 @@ public final class GlobalProjectMonitor {
         mEventListeners.remove(listener);
     }
 
-    private IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {
+    /**
+     * Adds a raw delta listener.
+     * @param listener The listener to receive the deltas.
+     */
+    public synchronized void addRawDeltaListener(IRawDeltaListener listener) {
+        mRawDeltaListeners.add(listener);
+    }
+
+    /**
+     * Removes an existing Raw Delta listener.
+     * @param listener the listener to remove.
+     */
+    public synchronized void removeRawDeltaListener(IRawDeltaListener listener) {
+        mRawDeltaListeners.remove(listener);
+    }
+
+    private final IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {
         /**
          * Processes the workspace resource change events.
          *
index ec8b717..6d15f89 100644 (file)
@@ -17,7 +17,6 @@
 package com.android.ide.eclipse.adt.internal.resources.manager;
 
 import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.resources.InlineResourceItem;
 import com.android.ide.common.resources.IntArrayWrapper;
 import com.android.ide.common.resources.ResourceFolder;
 import com.android.ide.common.resources.ResourceItem;
@@ -32,7 +31,6 @@ import com.android.util.Pair;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 
-import java.util.ArrayList;
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.List;
@@ -290,74 +288,5 @@ public class ProjectResources extends ResourceRepository {
         mResourceValueMap = resourceValueMap;
         mResIdValueToNameMap = resIdValueToNameMap;
         mStyleableValueToNameMap = styleableValueMap;
-        mergeIdResources();
-    }
-
-    @Override
-    protected void postUpdate() {
-        super.postUpdate();
-        mergeIdResources();
-    }
-
-    /**
-     * Merges the list of ID resource coming from R.java and the list of ID resources
-     * coming from XML declaration into the cached list {@link #mIdResourceList}.
-     */
-    void mergeIdResources() {
-        if (mResourceValueMap == null) {
-            return;
-        }
-
-        // get the current ID values
-        List<ResourceItem> resources = mResourceMap.get(ResourceType.ID);
-
-        // get the ID values coming from the R class.
-        Map<String, Integer> rResources = mResourceValueMap.get(ResourceType.ID);
-
-        if (rResources != null) {
-            Map<String, Integer> copy;
-
-            if (resources == null) {
-                resources = new ArrayList<ResourceItem>(rResources.entrySet().size());
-                mResourceMap.put(ResourceType.ID, resources);
-                copy = rResources;
-            } else {
-                // make a copy of the compiled Resources.
-                // As we loop on the full resources, we'll check with this copy map and remove
-                // from it all the resources we find in the full list.
-                // At the end, whatever is in the copy of the compile list is not in the full map,
-                // and should be added as inlined resource items.
-                copy = new HashMap<String, Integer>(rResources);
-
-                for (int i = 0 ; i < resources.size(); ) {
-                    ResourceItem item = resources.get(i);
-                    String name = item.getName();
-                    if (item.isDeclaredInline()) {
-                        // This ID is declared inline in the full resource map.
-                        // Check if it's also in the compiled version, in which case we can keep it.
-                        // Otherwise, if it doesn't exist in the compiled map, remove it from the
-                        // full map.
-                        // Since we're going to remove it from the copy map either way, we can use
-                        // remove to test if it's there
-                        if (copy.remove(name) != null) {
-                            // there is a match in the compiled list, do nothing, keep current one.
-                            i++;
-                        } else {
-                            // the ID is now gone, remove it from the list
-                            resources.remove(i);
-                        }
-                    } else {
-                        // not an inline item, remove it from the copy.
-                        copy.remove(name);
-                        i++;
-                    }
-                }
-            }
-
-            // now add what's left in copy to the list
-            for (String name : copy.keySet()) {
-                resources.add(new InlineResourceItem(name));
-            }
-        }
     }
 }
index 7935800..c014601 100644 (file)
@@ -23,9 +23,8 @@ import com.android.ide.common.resources.ResourceRepository;
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
-import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
-import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFolderListener;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
 import com.android.ide.eclipse.adt.io.IFileWrapper;
 import com.android.ide.eclipse.adt.io.IFolderWrapper;
@@ -41,6 +40,8 @@ import org.eclipse.core.resources.IMarkerDelta;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 
@@ -111,10 +112,7 @@ public final class ResourceManager {
     public static void setup(GlobalProjectMonitor monitor) {
         monitor.addResourceEventListener(sThis.mResourceEventListener);
         monitor.addProjectListener(sThis.mProjectListener);
-
-        int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
-        monitor.addFolderListener(sThis.mFolderListener, mask);
-        monitor.addFileListener(sThis.mFileListener, mask);
+        monitor.addRawDeltaListener(sThis.mRawDeltaListener);
 
         CompiledResourcesMonitor.setupMonitor(monitor);
     }
@@ -157,6 +155,45 @@ public final class ResourceManager {
         }
     }
 
+    /**
+     * Update the resource repository with a delta
+     * @param delta the resource changed delta to process.
+     */
+    public void processDelta(IResourceDelta delta) {
+        // Skip over deltas that don't fit our mask
+        int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
+        int kind = delta.getKind();
+        if ( (mask & kind) == 0) {
+            return;
+        }
+        // If our delta was handed to us from the PreCompiler then it's a single delta
+        // with lots of children. GlobalProjectMonitor will hand us a delta for each
+        // item in the tree of modifications so we only need to recurse into delta
+        // children if we're autobuilding.
+        if (ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding()) {
+            IResourceDelta[] children = delta.getAffectedChildren();
+            for (IResourceDelta child : children)  {
+                processDelta(child);
+            }
+        }
+
+        // Process this delta
+        IResource r = delta.getResource();
+        int type = r.getType();
+
+        if (type == IResource.FILE) {
+            updateFile((IFile)r, delta.getMarkerDeltas(), kind);
+        } else if (type == IResource.FOLDER) {
+            updateFolder((IFolder)r, kind);
+        } // We only care about files and folders.
+          // Project deltas are handled by our project listener
+    }
+
+    /**
+     * Private implementation of a resource event listener that registers with
+     * GlobalProjectMonitor.
+     *
+     */
     private class ResourceEventListener implements IResourceEventListener {
         private final List<IProject> mChangedProjects = new ArrayList<IProject>();
 
@@ -166,8 +203,6 @@ public final class ResourceManager {
                 synchronized (mMap) {
                     resources = mMap.get(project);
                 }
-
-                resources.postUpdate();
             }
 
             mChangedProjects.clear();
@@ -188,160 +223,150 @@ public final class ResourceManager {
      * Delegate listener for resource changes. This is called before and after any calls to the
      * project and file listeners (for a given resource change event).
      */
-    private ResourceEventListener mResourceEventListener = new ResourceEventListener();
-
+    private final ResourceEventListener mResourceEventListener = new ResourceEventListener();
 
     /**
-     * Implementation of the {@link IFolderListener} as an internal class so that the methods
-     * do not appear in the public API of {@link ResourceManager}.
+     * Update a resource folder that we know about
+     * @param folder the folder that was updated
+     * @param kind the delta type (added/removed/updated)
      */
-    private IFolderListener mFolderListener = new IFolderListener() {
-        public void folderChanged(IFolder folder, int kind) {
-            ProjectResources resources;
+    private void updateFolder(IFolder folder, int kind) {
+        ProjectResources resources;
 
-            final IProject project = folder.getProject();
+        final IProject project = folder.getProject();
 
-            try {
-                if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
-                    return;
-                }
-            } catch (CoreException e) {
-                // can't get the project nature? return!
+        try {
+            if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
                 return;
             }
+        } catch (CoreException e) {
+            // can't get the project nature? return!
+            return;
+        }
 
-            mResourceEventListener.addProject(project);
+        mResourceEventListener.addProject(project);
 
-            switch (kind) {
-                case IResourceDelta.ADDED:
-                    // checks if the folder is under res.
-                    IPath path = folder.getFullPath();
-
-                    // the path will be project/res/<something>
-                    if (path.segmentCount() == 3) {
-                        if (isInResFolder(path)) {
-                            // get the project and its resource object.
-                            synchronized (mMap) {
-                                resources = mMap.get(project);
-
-                                // if it doesn't exist, we create it.
-                                if (resources == null) {
-                                    resources = new ProjectResources(project);
-                                    mMap.put(project, resources);
-                                }
-                            }
+        switch (kind) {
+            case IResourceDelta.ADDED:
+                // checks if the folder is under res.
+                IPath path = folder.getFullPath();
 
-                            ResourceFolder newFolder = resources.processFolder(
-                                    new IFolderWrapper(folder));
-                            if (newFolder != null) {
-                                notifyListenerOnFolderChange(project, newFolder, kind);
+                // the path will be project/res/<something>
+                if (path.segmentCount() == 3) {
+                    if (isInResFolder(path)) {
+                        // get the project and its resource object.
+                        synchronized (mMap) {
+                            resources = mMap.get(project);
+
+                            // if it doesn't exist, we create it.
+                            if (resources == null) {
+                                resources = new ProjectResources(project);
+                                mMap.put(project, resources);
                             }
                         }
-                    }
-                    break;
-                case IResourceDelta.CHANGED:
-                    // only call the listeners.
-                    synchronized (mMap) {
-                        resources = mMap.get(folder.getProject());
-                    }
-                    if (resources != null) {
-                        ResourceFolder resFolder = resources.getResourceFolder(folder);
-                        if (resFolder != null) {
-                            notifyListenerOnFolderChange(project, resFolder, kind);
-                        }
-                    }
-                    break;
-                case IResourceDelta.REMOVED:
-                    synchronized (mMap) {
-                        resources = mMap.get(folder.getProject());
-                    }
-                    if (resources != null) {
-                        // lets get the folder type
-                        ResourceFolderType type = ResourceFolderType.getFolderType(
-                                folder.getName());
 
-                        ResourceFolder removedFolder = resources.removeFolder(type,
+                        ResourceFolder newFolder = resources.processFolder(
                                 new IFolderWrapper(folder));
-                        if (removedFolder != null) {
-                            notifyListenerOnFolderChange(project, removedFolder, kind);
+                        if (newFolder != null) {
+                            notifyListenerOnFolderChange(project, newFolder, kind);
                         }
                     }
-                    break;
-            }
+                }
+                break;
+            case IResourceDelta.CHANGED:
+                // only call the listeners.
+                synchronized (mMap) {
+                    resources = mMap.get(folder.getProject());
+                }
+                if (resources != null) {
+                    ResourceFolder resFolder = resources.getResourceFolder(folder);
+                    if (resFolder != null) {
+                        notifyListenerOnFolderChange(project, resFolder, kind);
+                    }
+                }
+                break;
+            case IResourceDelta.REMOVED:
+                synchronized (mMap) {
+                    resources = mMap.get(folder.getProject());
+                }
+                if (resources != null) {
+                    // lets get the folder type
+                    ResourceFolderType type = ResourceFolderType.getFolderType(
+                            folder.getName());
+
+                    ResourceFolder removedFolder = resources.removeFolder(type,
+                            new IFolderWrapper(folder));
+                    if (removedFolder != null) {
+                        notifyListenerOnFolderChange(project, removedFolder, kind);
+                    }
+                }
+                break;
         }
-    };
+    }
 
     /**
-     * Implementation of the {@link IFileListener} as an internal class so that the methods
-     * do not appear in the public API of {@link ResourceManager}.
+     * Called when a delta indicates that a file has changed.
+     * Depending on the file being changed, and the type of change
+     * (ADDED, REMOVED, CHANGED), the file change is processed to update the resource
+     * manager data.
+     *
+     * @param file The file that changed.
+     * @param markerDeltas The marker deltas for the file.
+     * @param kind The change kind. This is equivalent to
+     * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
      */
-    private IFileListener mFileListener = new IFileListener() {
-        /* (non-Javadoc)
-         * Sent when a file changed. Depending on the file being changed, and the type of change
-         * (ADDED, REMOVED, CHANGED), the file change is processed to update the resource
-         * manager data.
-         *
-         * @param file The file that changed.
-         * @param markerDeltas The marker deltas for the file.
-         * @param kind The change kind. This is equivalent to
-         * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
-         *
-         * @see IFileListener#fileChanged
-         */
-        public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
-            final IProject project = file.getProject();
+    private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind) {
+        final IProject project = file.getProject();
 
-            try {
-                if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
-                    return;
-                }
-            } catch (CoreException e) {
-                // can't get the project nature? return!
+        try {
+            if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
                 return;
             }
+        } catch (CoreException e) {
+            // can't get the project nature? return!
+            return;
+        }
 
-            // get the project resources
-            ProjectResources resources;
-            synchronized (mMap) {
-                resources = mMap.get(project);
-            }
+        // get the project resources
+        ProjectResources resources;
+        synchronized (mMap) {
+            resources = mMap.get(project);
+        }
 
-            if (resources == null) {
-                return;
-            }
+        if (resources == null) {
+            return;
+        }
 
-            // checks if the file is under res/something.
-            IPath path = file.getFullPath();
-
-            if (path.segmentCount() == 4) {
-                if (isInResFolder(path)) {
-                    IContainer container = file.getParent();
-                    if (container instanceof IFolder) {
-
-                        ResourceFolder folder = resources.getResourceFolder(
-                                (IFolder)container);
-
-                        // folder can be null as when the whole folder is deleted, the
-                        // REMOVED event for the folder comes first. In this case, the
-                        // folder will have taken care of things.
-                        if (folder != null) {
-                            ResourceFile resFile = folder.processFile(
-                                    new IFileWrapper(file),
-                                    ResourceHelper.getResourceDeltaKind(kind));
-                            notifyListenerOnFileChange(project, resFile, kind);
-                        }
+        // checks if the file is under res/something.
+        IPath path = file.getFullPath();
+
+        if (path.segmentCount() == 4) {
+            if (isInResFolder(path)) {
+                IContainer container = file.getParent();
+                if (container instanceof IFolder) {
+
+                    ResourceFolder folder = resources.getResourceFolder(
+                            (IFolder)container);
+
+                    // folder can be null as when the whole folder is deleted, the
+                    // REMOVED event for the folder comes first. In this case, the
+                    // folder will have taken care of things.
+                    if (folder != null) {
+                        ResourceFile resFile = folder.processFile(
+                                new IFileWrapper(file),
+                                ResourceHelper.getResourceDeltaKind(kind));
+                        notifyListenerOnFileChange(project, resFile, kind);
                     }
                 }
             }
         }
-    };
-
+    }
 
     /**
      * Implementation of the {@link IProjectListener} as an internal class so that the methods
      * do not appear in the public API of {@link ResourceManager}.
      */
-    private IProjectListener mProjectListener = new IProjectListener() {
+    private final IProjectListener mProjectListener = new IProjectListener() {
         public void projectClosed(IProject project) {
             synchronized (mMap) {
                 mMap.remove(project);
@@ -368,6 +393,21 @@ public final class ResourceManager {
     };
 
     /**
+     * Implementation of {@link IRawDeltaListener} as an internal class so that the methods
+     * do not appear in the public API of {@link ResourceManager}. Delta processing can be
+     * accessed through the {@link ResourceManager#visitDelta(IResourceDelta delta)} method.
+     */
+    private final IRawDeltaListener mRawDeltaListener = new IRawDeltaListener() {
+        public void visitDelta(IResourceDelta delta) {
+            // If we're autobuilding, then PreCompilerBuilder will pass us deltas
+            if (ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding()) {
+                return;
+            }
+            processDelta(delta);
+        }
+    };
+
+    /**
      * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
      */
     public ResourceFolder getResourceFolder(IFile file) {
@@ -475,8 +515,6 @@ public final class ResourceManager {
                             }
                         }
                     }
-
-                    projectResources.postUpdate();
                 } catch (CoreException e) {
                     // This happens if the project is closed or if the folder doesn't exist.
                     // Since we already test for that, we can ignore this exception.
diff --git a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
new file mode 100644 (file)
index 0000000..fa8d0e7
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.resources.ResourceType;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Represents a resource file that also generates ID resources.
+ * <p/>
+ * This is typically an XML file in res/layout or res/menu
+ */
+public final class IdGeneratingResourceFile extends ResourceFile
+                                            implements IValueResourceRepository {
+
+    private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
+    static {
+        sParserFactory.setNamespaceAware(true);
+    }
+
+    private final Map<String, ResourceValue> mIdResources =
+        new HashMap<String, ResourceValue>();
+
+    private final Collection<ResourceType> mResourceTypeList;
+
+    private final String mFileName;
+
+    private final ResourceType mFileType;
+
+    private final ResourceValue mFileValue;
+
+    public IdGeneratingResourceFile(IAbstractFile file, ResourceFolder folder, ResourceType type) {
+        super(file, folder);
+
+        mFileType = type;
+
+        // Set up our resource types
+        mResourceTypeList = new HashSet<ResourceType>();
+        mResourceTypeList.add(mFileType);
+        mResourceTypeList.add(ResourceType.ID);
+
+        // compute the resource name
+        mFileName = getFileName(type);
+
+        // Get the resource value of this file as a whole layout
+        mFileValue = getFileValue(file,folder);
+    }
+
+    @Override
+    protected void load() {
+        // Parse the file and look for @+id/ entries
+        parseFileForIds();
+
+        // create the resource items in the repository
+        updateResourceItems();
+    }
+
+    @Override
+    protected void update() {
+        // remove this file from all existing ResourceItem.
+        getFolder().getRepository().removeFile(mResourceTypeList, this);
+
+        // reset current content.
+        mIdResources.clear();
+
+        // need to parse the file and find the IDs.
+        parseFileForIds();
+
+        // Notify the repository about any changes
+        updateResourceItems();
+    }
+
+    @Override
+    protected void dispose() {
+        // Remove declarations from this file from the repository
+        getFolder().getRepository().removeFile(mResourceTypeList, this);
+    }
+
+    @Override
+    public Collection<ResourceType> getResourceTypes() {
+        return mResourceTypeList;
+    }
+
+    @Override
+    public boolean hasResources(ResourceType type) {
+        return (type == mFileType) || (type == ResourceType.ID && !mIdResources.isEmpty());
+    }
+
+    @Override
+    public ResourceValue getValue(ResourceType type, String name) {
+        // Check to see if they're asking for one of the right types:
+        if (type != mFileType && type != ResourceType.ID) {
+            return null;
+        }
+
+        // If they're looking for a resource of this type with this name give them the whole file
+        if (type == mFileType && name.equals(mFileName)) {
+            return mFileValue;
+        } else {
+            // Otherwise try to return them an ID
+            // the map will return null if it's not found
+            return mIdResources.get(name);
+        }
+    }
+
+    /**
+     * Looks through the file represented for Ids and adds them to
+     * our id repository
+     */
+    private void parseFileForIds() {
+        try {
+            SAXParser parser = sParserFactory.newSAXParser();
+            parser.parse(getFile().getContents(), new IdResourceParser(this, isFramework()));
+        } catch (ParserConfigurationException e) {
+        } catch (SAXException e) {
+        } catch (IOException e) {
+        } catch (StreamException e) {
+        }
+    }
+
+    /**
+     * Add the resources represented by this file to the repository
+     */
+    private void updateResourceItems() {
+        ResourceRepository repository = getRepository();
+
+        // First add this as a layout file
+        ResourceItem item = repository.getResourceItem(mFileType, mFileName);
+        item.add(this);
+
+        // Now iterate through our IDs and add
+        for (String idName : mIdResources.keySet()) {
+            item = repository.getResourceItem(ResourceType.ID, idName);
+            // add this file to the list of files generating ID resources.
+            item.add(this);
+        }
+    }
+
+    /**
+     * Returns the resource value associated with this whole file as a layout resource
+     * @param file the file handler that represents this file
+     * @param folder the folder this file is under
+     * @return a resource value associated with this layout
+     */
+    private ResourceValue getFileValue(IAbstractFile file, ResourceFolder folder) {
+        // test if there's a density qualifier associated with the resource
+        DensityQualifier qualifier = folder.getConfiguration().getDensityQualifier();
+
+        ResourceValue value;
+        if (qualifier == null) {
+            value = new ResourceValue(mFileType, mFileName,
+                    file.getOsLocation(), isFramework());
+        } else {
+            value = new DensityBasedResourceValue(
+                    mFileType, mFileName,
+                    file.getOsLocation(),
+                    qualifier.getValue(),
+                    isFramework());
+        }
+        return value;
+    }
+
+
+    /**
+     * Returns the name of this resource.
+     */
+    private String getFileName(ResourceType type) {
+        // get the name from the filename.
+        String name = getFile().getName();
+
+        int pos = name.indexOf('.');
+        if (pos != -1) {
+            name = name.substring(0, pos);
+        }
+
+        return name;
+    }
+
+    public void addResourceValue(ResourceValue value) {
+        // Just overwrite collisions. We're only interested in the unique
+        // IDs declared
+        mIdResources.put(value.getName(), value);
+    }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/IdResourceParser.java b/ide_common/src/com/android/ide/common/resources/IdResourceParser.java
new file mode 100644 (file)
index 0000000..27195a9
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.resources.ResourceType;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class IdResourceParser extends DefaultHandler {
+
+    private final IValueResourceRepository mRepository;
+    private final boolean mIsFramework;
+
+    public IdResourceParser(IValueResourceRepository repository, boolean isFramework) {
+        super();
+        mRepository = repository;
+        mIsFramework = isFramework;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+            throws SAXException {
+        for (int i = 0; i < attributes.getLength(); ++i) {
+            // Let's look up the value to look for the @+*id/ pattern
+            String candidate = attributes.getValue(i);
+            // Right now the only things that start with the @+ pattern are IDs. If this changes
+            // in the future we'll have to change this line
+            if (candidate != null && candidate.startsWith("@+")) {
+                // Strip out the @+id/ or @+android:id/ section
+                String id = candidate.substring(candidate.indexOf('/') + 1);
+                ResourceValue newId = new ResourceValue(ResourceType.ID, id, mIsFramework);
+                mRepository.addResourceValue(newId);
+            }
+        }
+    }
+}
index abdf200..135fbeb 100644 (file)
@@ -73,7 +73,9 @@ public final class ResourceFolder implements Configurable {
                 // create a ResourceFile for it.
 
                 // check if that's a single or multi resource type folder. For now we define this by
-                // the number of possible resource type output by files in the folder. This does
+                // the number of possible resource type output by files in the folder.
+                // We have a special case for layout/menu folders which can also generate IDs.
+                // This does
                 // not make the difference between several resource types from a single file or
                 // the ability to have 2 files in the same folder generating 2 different types of
                 // resource. The former is handled by MultiResourceFile properly while we don't
@@ -82,6 +84,10 @@ public final class ResourceFolder implements Configurable {
 
                 if (types.size() == 1) {
                     resFile = new SingleResourceFile(file, this);
+                } else if (types.contains(ResourceType.LAYOUT)){
+                    resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT);
+                } else if (types.contains(ResourceType.MENU)) {
+                    resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU);
                 } else {
                     resFile = new MultiResourceFile(file, this);
                 }