aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java655
1 files changed, 655 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
new file mode 100644
index 000000000..e407b6a78
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.eclipse.adt.internal.resources.manager;
+
+import com.android.SdkConstants;
+import com.android.ide.common.resources.FrameworkResources;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ScanningContext;
+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.IProjectListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener;
+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;
+import com.android.ide.eclipse.adt.io.IFolderWrapper;
+import com.android.io.FolderWrapper;
+import com.android.resources.ResourceFolderType;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+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;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.QualifiedName;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The ResourceManager tracks resources for all opened projects.
+ * <p/>
+ * It provide direct access to all the resources of a project as a {@link ProjectResources}
+ * object that allows accessing the resources through their file representation or as Android
+ * resources (similar to what is seen by an Android application).
+ * <p/>
+ * The ResourceManager automatically tracks file changes to update its internal representation
+ * of the resources so that they are always up to date.
+ * <p/>
+ * It also gives access to a monitor that is more resource oriented than the
+ * {@link GlobalProjectMonitor}.
+ * This monitor will let you track resource changes by giving you direct access to
+ * {@link ResourceFile}, or {@link ResourceFolder}.
+ *
+ * @see ProjectResources
+ */
+public final class ResourceManager {
+ public final static boolean DEBUG = false;
+
+ private final static ResourceManager sThis = new ResourceManager();
+
+ /**
+ * Map associating project resource with project objects.
+ * <p/><b>All accesses must be inside a synchronized(mMap) block</b>, and do as a little as
+ * possible and <b>not call out to other classes</b>.
+ */
+ private final Map<IProject, ProjectResources> mMap =
+ new HashMap<IProject, ProjectResources>();
+
+ /**
+ * Interface to be notified of resource changes.
+ *
+ * @see ResourceManager#addListener(IResourceListener)
+ * @see ResourceManager#removeListener(IResourceListener)
+ */
+ public interface IResourceListener {
+ /**
+ * Notification for resource file change.
+ * @param project the project of the file.
+ * @param file the {@link ResourceFile} representing the file.
+ * @param eventType the type of event. See {@link IResourceDelta}.
+ */
+ void fileChanged(IProject project, ResourceFile file, int eventType);
+ /**
+ * Notification for resource folder change.
+ * @param project the project of the file.
+ * @param folder the {@link ResourceFolder} representing the folder.
+ * @param eventType the type of event. See {@link IResourceDelta}.
+ */
+ void folderChanged(IProject project, ResourceFolder folder, int eventType);
+ }
+
+ private final ArrayList<IResourceListener> mListeners = new ArrayList<IResourceListener>();
+
+ /**
+ * Sets up the resource manager with the global project monitor.
+ * @param monitor The global project monitor
+ */
+ public static void setup(GlobalProjectMonitor monitor) {
+ monitor.addProjectListener(sThis.mProjectListener);
+ monitor.addRawDeltaListener(sThis.mRawDeltaListener);
+
+ CompiledResourcesMonitor.setupMonitor(monitor);
+ }
+
+ /**
+ * Returns the singleton instance.
+ */
+ public static ResourceManager getInstance() {
+ return sThis;
+ }
+
+ /**
+ * Adds a new {@link IResourceListener} to be notified of resource changes.
+ * @param listener the listener to be added.
+ */
+ public void addListener(IResourceListener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes an {@link IResourceListener}, so that it's not notified of resource changes anymore.
+ * @param listener the listener to be removed.
+ */
+ public void removeListener(IResourceListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the resources of a project.
+ * @param project The project
+ * @return a ProjectResources object
+ */
+ public ProjectResources getProjectResources(IProject project) {
+ synchronized (mMap) {
+ ProjectResources resources = mMap.get(project);
+
+ if (resources == null) {
+ resources = ProjectResources.create(project);
+ mMap.put(project, resources);
+ }
+
+ return resources;
+ }
+ }
+
+ /**
+ * Update the resource repository with a delta
+ *
+ * @param delta the resource changed delta to process.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ */
+ public void processDelta(IResourceDelta delta, IdeScanningContext context) {
+ doProcessDelta(delta, context);
+
+ // when a project is added to the workspace it is possible this is called before the
+ // repo is actually created so this will return null.
+ ResourceRepository repo = context.getRepository();
+ if (repo != null) {
+ repo.postUpdateCleanUp();
+ }
+ }
+
+ /**
+ * Update the resource repository with a delta
+ *
+ * @param delta the resource changed delta to process.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ */
+ private void doProcessDelta(IResourceDelta delta, IdeScanningContext context) {
+ // 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;
+ }
+
+ // Process this delta first as we need to make sure new folders are created before
+ // we process their content
+ IResource r = delta.getResource();
+ int type = r.getType();
+
+ if (type == IResource.FILE) {
+ context.startScanning(r);
+ updateFile((IFile)r, delta.getMarkerDeltas(), kind, context);
+ context.finishScanning(r);
+ } else if (type == IResource.FOLDER) {
+ updateFolder((IFolder)r, kind, context);
+ } // We only care about files and folders.
+ // Project deltas are handled by our project listener
+
+ // Now, process children recursively
+ IResourceDelta[] children = delta.getAffectedChildren();
+ for (IResourceDelta child : children) {
+ processDelta(child, context);
+ }
+ }
+
+ /**
+ * Update a resource folder that we know about
+ * @param folder the folder that was updated
+ * @param kind the delta type (added/removed/updated)
+ */
+ private void updateFolder(IFolder folder, int kind, IdeScanningContext context) {
+ ProjectResources resources;
+
+ final IProject project = folder.getProject();
+
+ try {
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ return;
+ }
+ } catch (CoreException e) {
+ // can't get the project nature? return!
+ return;
+ }
+
+ 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 = ProjectResources.create(project);
+ mMap.put(project, resources);
+ }
+ }
+
+ ResourceFolder newFolder = resources.processFolder(
+ new IFolderWrapper(folder));
+ if (newFolder != null) {
+ notifyListenerOnFolderChange(project, newFolder, kind);
+ }
+ }
+ }
+ 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());
+
+ context.startScanning(folder);
+ ResourceFolder removedFolder = resources.removeFolder(type,
+ new IFolderWrapper(folder), context);
+ context.finishScanning(folder);
+ if (removedFolder != null) {
+ notifyListenerOnFolderChange(project, removedFolder, kind);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * 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)}
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ */
+ private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind,
+ ScanningContext context) {
+ final IProject project = file.getProject();
+
+ 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);
+ }
+
+ if (resources == null) {
+ return;
+ }
+
+ // checks if the file is under res/something or bin/res/something
+ IPath path = file.getFullPath();
+
+ if (path.segmentCount() == 4 || path.segmentCount() == 5) {
+ 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), context);
+ 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 final IProjectListener mProjectListener = new IProjectListener() {
+ @Override
+ public void projectClosed(IProject project) {
+ synchronized (mMap) {
+ mMap.remove(project);
+ }
+ }
+
+ @Override
+ public void projectDeleted(IProject project) {
+ synchronized (mMap) {
+ mMap.remove(project);
+ }
+ }
+
+ @Override
+ public void projectOpened(IProject project) {
+ createProject(project);
+ }
+
+ @Override
+ public void projectOpenedWithWorkspace(IProject project) {
+ createProject(project);
+ }
+
+ @Override
+ public void allProjectsOpenedWithWorkspace() {
+ // nothing to do.
+ }
+
+ @Override
+ public void projectRenamed(IProject project, IPath from) {
+ // renamed project get a delete/open event too, so this can be ignored.
+ }
+ };
+
+ /**
+ * 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() {
+ @Override
+ public void visitDelta(IResourceDelta workspaceDelta) {
+ // If we're auto-building, then PreCompilerBuilder will pass us deltas and
+ // they will be processed as part of the build.
+ if (isAutoBuilding()) {
+ return;
+ }
+
+ // When *not* auto building, we need to process the deltas immediately on save,
+ // even if the user is not building yet, such that for example resource ids
+ // are updated in the resource repositories so rendering etc. can work for
+ // those new ids.
+
+ IResourceDelta[] projectDeltas = workspaceDelta.getAffectedChildren();
+ for (IResourceDelta delta : projectDeltas) {
+ if (delta.getResource() instanceof IProject) {
+ IProject project = (IProject) delta.getResource();
+
+ try {
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ continue;
+ }
+ } catch (CoreException e) {
+ // only happens if the project is closed or doesn't exist.
+ }
+
+ IdeScanningContext context =
+ new IdeScanningContext(getProjectResources(project), project, true);
+
+ processDelta(delta, context);
+
+ Collection<IProject> projects = context.getAaptRequestedProjects();
+ if (projects != null) {
+ for (IProject p : projects) {
+ markAaptRequested(p);
+ }
+ }
+ } else {
+ AdtPlugin.log(IStatus.WARNING, "Unexpected delta type: %1$s",
+ delta.getResource().toString());
+ }
+ }
+ }
+ };
+
+ /**
+ * Returns the {@link ResourceFolder} for the given file or <code>null</code> if none exists.
+ */
+ public ResourceFolder getResourceFolder(IFile file) {
+ IContainer container = file.getParent();
+ if (container.getType() == IResource.FOLDER) {
+ IFolder parent = (IFolder)container;
+ IProject project = file.getProject();
+
+ ProjectResources resources = getProjectResources(project);
+ if (resources != null) {
+ return resources.getResourceFolder(parent);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link ResourceFolder} for the given folder or <code>null</code> if none exists.
+ */
+ public ResourceFolder getResourceFolder(IFolder folder) {
+ IProject project = folder.getProject();
+
+ ProjectResources resources = getProjectResources(project);
+ if (resources != null) {
+ return resources.getResourceFolder(folder);
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads and returns the resources for a given {@link IAndroidTarget}
+ * @param androidTarget the target from which to load the framework resources
+ */
+ public ResourceRepository loadFrameworkResources(IAndroidTarget androidTarget) {
+ String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES);
+
+ FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath);
+ if (frameworkRes.exists()) {
+ FrameworkResources resources = new FrameworkResources(frameworkRes);
+
+ resources.loadResources();
+ resources.loadPublicResources(AdtPlugin.getDefault());
+ return resources;
+ }
+
+ return null;
+ }
+
+ /**
+ * Initial project parsing to gather resource info.
+ * @param project
+ */
+ private void createProject(IProject project) {
+ if (project.isOpen()) {
+ synchronized (mMap) {
+ ProjectResources projectResources = mMap.get(project);
+ if (projectResources == null) {
+ projectResources = ProjectResources.create(project);
+ mMap.put(project, projectResources);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns true if the path is under /project/res/
+ * @param path a workspace relative path
+ * @return true if the path is under /project res/
+ */
+ private boolean isInResFolder(IPath path) {
+ return SdkConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1));
+ }
+
+ private void notifyListenerOnFolderChange(IProject project, ResourceFolder folder,
+ int eventType) {
+ synchronized (mListeners) {
+ for (IResourceListener listener : mListeners) {
+ try {
+ listener.folderChanged(project, folder, eventType);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,
+ "Failed to execute ResourceManager.IResouceListener.folderChanged()"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void notifyListenerOnFileChange(IProject project, ResourceFile file, int eventType) {
+ synchronized (mListeners) {
+ for (IResourceListener listener : mListeners) {
+ try {
+ listener.fileChanged(project, file, eventType);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,
+ "Failed to execute ResourceManager.IResouceListener.fileChanged()"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ /**
+ * Private constructor to enforce singleton design.
+ */
+ private ResourceManager() {
+ }
+
+ // debug only
+ @SuppressWarnings("unused")
+ private String getKindString(int kind) {
+ if (DEBUG) {
+ switch (kind) {
+ case IResourceDelta.ADDED: return "ADDED";
+ case IResourceDelta.REMOVED: return "REMOVED";
+ case IResourceDelta.CHANGED: return "CHANGED";
+ }
+ }
+
+ return Integer.toString(kind);
+ }
+
+ /**
+ * Returns true if the Project > Build Automatically option is turned on
+ * (default).
+ *
+ * @return true if the Project > Build Automatically option is turned on
+ * (default).
+ */
+ public static boolean isAutoBuilding() {
+ return ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
+ }
+
+ /** Qualified name for the per-project persistent property "needs aapt" */
+ private final static QualifiedName NEED_AAPT = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "aapt");//$NON-NLS-1$
+
+ /**
+ * Mark the given project, and any projects which depend on it as a library
+ * project, as needing a full aapt build the next time the project is built.
+ *
+ * @param project the project to mark as needing aapt
+ */
+ public static void markAaptRequested(IProject project) {
+ try {
+ String needsAapt = Boolean.TRUE.toString();
+ project.setPersistentProperty(NEED_AAPT, needsAapt);
+
+ ProjectState state = Sdk.getProjectState(project);
+ if (state.isLibrary()) {
+ // For library projects also mark the dependent projects as needing full aapt
+ for (ProjectState parent : state.getFullParentProjects()) {
+ IProject parentProject = parent.getProject();
+ // Mark the project, but only if it's open. Resource#setPersistentProperty
+ // only works on open projects.
+ if (parentProject.isOpen()) {
+ parentProject.setPersistentProperty(NEED_AAPT, needsAapt);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Clear the "needs aapt" flag set by {@link #markAaptRequested(IProject)}.
+ * This is usually called when a project is built. Note that this will only
+ * clean the build flag on the given project, not on any downstream projects
+ * that depend on this project as a library project.
+ *
+ * @param project the project to clear from the needs aapt list
+ */
+ public static void clearAaptRequest(IProject project) {
+ try {
+ project.setPersistentProperty(NEED_AAPT, null);
+ // Note that even if this project is a library project, we -don't- clear
+ // the aapt flags on the dependent projects since they may still depend
+ // on other dirty projects. When they are built, they will issue their
+ // own clear flag requests.
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Returns whether the given project needs a full aapt build.
+ *
+ * @param project the project to check
+ * @return true if the project needs a full aapt run
+ */
+ public static boolean isAaptRequested(IProject project) {
+ try {
+ String b = project.getPersistentProperty(NEED_AAPT);
+ return b != null && Boolean.valueOf(b);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return false;
+ }
+}