aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java313
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/DynamicIdMap.java75
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java546
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java234
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java376
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java271
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java655
7 files changed, 2470 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
new file mode 100644
index 000000000..ab5ae4070
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
@@ -0,0 +1,313 @@
+/*
+ * 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.IntArrayWrapper;
+import com.android.ide.common.xml.ManifestData;
+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;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.eclipse.core.resources.IFile;
+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.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * A monitor for the compiled resources. This only monitors changes in the resources of type
+ * {@link ResourceType#ID}.
+ */
+public final class CompiledResourcesMonitor implements IFileListener, IProjectListener {
+
+ private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor();
+
+ /**
+ * Sets up the monitoring system.
+ * @param monitor The main Resource Monitor.
+ */
+ public static void setupMonitor(GlobalProjectMonitor monitor) {
+ monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED);
+ monitor.addProjectListener(sThis);
+ }
+
+ /**
+ * private constructor to prevent construction.
+ */
+ private CompiledResourcesMonitor() {
+ }
+
+
+ /* (non-Javadoc)
+ * Sent when a file changed : if the file is the R class, then it is parsed again to update
+ * the internal 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
+ */
+ @Override
+ public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (!isAndroidProject || flags == IResourceDelta.MARKERS) {
+ // Not Android or only the markers changed: not relevant
+ return;
+ }
+
+ IProject project = file.getProject();
+
+ if (file.getName().equals(SdkConstants.FN_COMPILED_RESOURCE_CLASS)) {
+ // create the classname
+ String className = getRClassName(project);
+ if (className == null) {
+ // We need to abort.
+ AdtPlugin.log(IStatus.ERROR,
+ "fileChanged: failed to find manifest package for project %1$s", //$NON-NLS-1$
+ project.getName());
+ return;
+ }
+ // path will begin with /projectName/bin/classes so we'll ignore that
+ IPath relativeClassPath = file.getFullPath().removeFirstSegments(3);
+ if (packagePathMatches(relativeClassPath.toString(), className)) {
+ loadAndParseRClass(project, className);
+ }
+ }
+ }
+
+ /**
+ * Check to see if the package section of the given path matches the packageName.
+ * For example, /project/bin/classes/com/foo/app/R.class should match com.foo.app.R
+ * @param path the pathname of the file to look at
+ * @param packageName the package qualified name of the class
+ * @return true if the package section of the path matches the package qualified name
+ */
+ private boolean packagePathMatches(String path, String packageName) {
+ // First strip the ".class" off the end of the path
+ String pathWithoutExtension = path.substring(0, path.indexOf(SdkConstants.DOT_CLASS));
+
+ // then split the components of each path by their separators
+ String [] pathArray = pathWithoutExtension.split(Pattern.quote(File.separator));
+ String [] packageArray = packageName.split(AdtConstants.RE_DOT);
+
+
+ int pathIndex = 0;
+ int packageIndex = 0;
+
+ while (pathIndex < pathArray.length && packageIndex < packageArray.length) {
+ if (pathArray[pathIndex].equals(packageArray[packageIndex]) == false) {
+ return false;
+ }
+ pathIndex++;
+ packageIndex++;
+ }
+ // We may have matched all the way up to this point, but we're not sure it's a match
+ // unless BOTH paths done
+ return (pathIndex == pathArray.length && packageIndex == packageArray.length);
+ }
+
+ /**
+ * Processes project close event.
+ */
+ @Override
+ public void projectClosed(IProject project) {
+ // the ProjectResources object will be removed by the ResourceManager.
+ }
+
+ /**
+ * Processes project delete event.
+ */
+ @Override
+ public void projectDeleted(IProject project) {
+ // the ProjectResources object will be removed by the ResourceManager.
+ }
+
+ /**
+ * Processes project open event.
+ */
+ @Override
+ public void projectOpened(IProject project) {
+ // when the project is opened, we get an ADDED event for each file, so we don't
+ // need to do anything here.
+ }
+
+ @Override
+ public void projectRenamed(IProject project, IPath from) {
+ // renamed projects also trigger delete/open event,
+ // so nothing to be done here.
+ }
+
+ /**
+ * Processes existing project at init time.
+ */
+ @Override
+ public void projectOpenedWithWorkspace(IProject project) {
+ try {
+ // check this is an android project
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
+ String className = getRClassName(project);
+ // Find the classname
+ if (className == null) {
+ // We need to abort.
+ AdtPlugin.log(IStatus.ERROR,
+ "projectOpenedWithWorkspace: failed to find manifest package for project %1$s", //$NON-NLS-1$
+ project.getName());
+ return;
+ }
+ loadAndParseRClass(project, className);
+ }
+ } catch (CoreException e) {
+ // pass
+ }
+ }
+
+ @Override
+ public void allProjectsOpenedWithWorkspace() {
+ // nothing to do.
+ }
+
+
+ private void loadAndParseRClass(IProject project, String className) {
+ try {
+ // first check there's a ProjectResources to store the content
+ ProjectResources projectResources = ResourceManager.getInstance().getProjectResources(
+ project);
+
+ if (projectResources != null) {
+ // create a temporary class loader to load the class
+ ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */,
+ project);
+
+ try {
+ Class<?> clazz = loader.loadClass(className);
+
+ if (clazz != null) {
+ // create the maps to store the result of the parsing
+ Map<ResourceType, Map<String, Integer>> resourceValueMap =
+ new EnumMap<ResourceType, Map<String, Integer>>(ResourceType.class);
+ Map<Integer, Pair<ResourceType, String>> genericValueToNameMap =
+ new HashMap<Integer, Pair<ResourceType, String>>();
+ Map<IntArrayWrapper, String> styleableValueToNameMap =
+ new HashMap<IntArrayWrapper, String>();
+
+ // parse the class
+ if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap,
+ resourceValueMap)) {
+ // now we associate the maps to the project.
+ projectResources.setCompiledResources(genericValueToNameMap,
+ styleableValueToNameMap, resourceValueMap);
+ }
+ }
+ } catch (Error e) {
+ // Log this error with the class name we're trying to load and abort.
+ AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ // pass
+ }
+ }
+
+ /**
+ * Parses a R class, and fills maps.
+ * @param rClass the class to parse
+ * @param genericValueToNameMap
+ * @param styleableValueToNameMap
+ * @param resourceValueMap
+ * @return True if we managed to parse the R class.
+ */
+ private boolean parseClass(Class<?> rClass,
+ Map<Integer, Pair<ResourceType, String>> genericValueToNameMap,
+ Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType,
+ Map<String, Integer>> resourceValueMap) {
+ try {
+ for (Class<?> inner : rClass.getDeclaredClasses()) {
+ String resTypeName = inner.getSimpleName();
+ ResourceType resType = ResourceType.getEnum(resTypeName);
+
+ if (resType != null) {
+ Map<String, Integer> fullMap = new HashMap<String, Integer>();
+ resourceValueMap.put(resType, fullMap);
+
+ for (Field f : inner.getDeclaredFields()) {
+ // only process static final fields.
+ int modifiers = f.getModifiers();
+ if (Modifier.isStatic(modifiers)) {
+ Class<?> type = f.getType();
+ if (type.isArray() && type.getComponentType() == int.class) {
+ // if the object is an int[] we put it in the styleable map
+ styleableValueToNameMap.put(
+ new IntArrayWrapper((int[]) f.get(null)),
+ f.getName());
+ } else if (type == int.class) {
+ Integer value = (Integer) f.get(null);
+ genericValueToNameMap.put(value, Pair.of(resType, f.getName()));
+ fullMap.put(f.getName(), value);
+ } else {
+ assert false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ } catch (IllegalArgumentException e) {
+ } catch (IllegalAccessException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Returns the class name of the R class, based on the project's manifest's package.
+ *
+ * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest.
+ */
+ private String getRClassName(IProject project) {
+ IFile manifestFile = ProjectHelper.getManifest(project);
+ if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) {
+ ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
+ if (data != null) {
+ String javaPackage = data.getPackage();
+ return javaPackage + ".R"; //$NON-NLS-1$
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/DynamicIdMap.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/DynamicIdMap.java
new file mode 100644
index 000000000..7bab4fd54
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/DynamicIdMap.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.resources.ResourceType;
+import com.android.util.Pair;
+import com.android.utils.SparseArray;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DynamicIdMap {
+
+ private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<Pair<ResourceType, String>, Integer>();
+ private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<Pair<ResourceType, String>>();
+ private int mDynamicSeed;
+
+ public DynamicIdMap(int seed) {
+ mDynamicSeed = seed;
+ }
+
+ public void reset(int seed) {
+ mDynamicIds.clear();
+ mRevDynamicIds.clear();
+ mDynamicSeed = seed;
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource
+ * @return an integer.
+ */
+ public Integer getId(ResourceType type, String name) {
+ return getId(Pair.of(type, name));
+ }
+
+ /**
+ * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
+ * already exist.
+ *
+ * @param resource the type/name of the resource
+ * @return an integer.
+ */
+ public Integer getId(Pair<ResourceType, String> resource) {
+ Integer value = mDynamicIds.get(resource);
+ if (value == null) {
+ value = Integer.valueOf(++mDynamicSeed);
+ mDynamicIds.put(resource, value);
+ mRevDynamicIds.put(value, resource);
+ }
+
+ return value;
+ }
+
+ public Pair<ResourceType, String> resolveId(int id) {
+ return mRevDynamicIds.get(id);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
new file mode 100644
index 000000000..674a601d0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
@@ -0,0 +1,546 @@
+/*
+ * 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.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+
+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.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+
+import java.util.ArrayList;
+
+/**
+ * The Global Project Monitor tracks project file changes, and forward them to simple project,
+ * file, and folder listeners.
+ * Those listeners can be setup with masks to listen to particular events.
+ * <p/>
+ * To track project resource changes, use the monitor in the {@link ResourceManager}. It is more
+ * efficient and while the global ProjectMonitor can track any file, deleted resource files
+ * cannot be matched to previous {@link ResourceFile} or {@link ResourceFolder} objects by the
+ * time the listeners get the event notifications.
+ *
+ * @see IProjectListener
+ * @see IFolderListener
+ * @see IFileListener
+ */
+public final class GlobalProjectMonitor {
+
+ private final static GlobalProjectMonitor sThis = new GlobalProjectMonitor();
+
+ /**
+ * Classes which implement this interface provide a method that deals
+ * with file change events.
+ */
+ public interface IFileListener {
+ /**
+ * Sent when a file changed.
+ *
+ * @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 extension the extension of the file or null if the file does
+ * not have an extension
+ * @param flags the {@link IResourceDelta#getFlags()} value with details
+ * on what changed in the file
+ * @param isAndroidProject whether the parent project is an Android Project
+ */
+ public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject);
+ }
+
+ /**
+ * Classes which implements this interface provide methods dealing with project events.
+ */
+ public interface IProjectListener {
+ /**
+ * Sent for each opened android project at the time the listener is put in place.
+ * @param project the opened project.
+ */
+ public void projectOpenedWithWorkspace(IProject project);
+
+ /**
+ * Sent once after all Android projects have been opened,
+ * at the time the listener is put in place.
+ * <p/>
+ * This is called after {@link #projectOpenedWithWorkspace(IProject)} has
+ * been called on all known Android projects.
+ */
+ public void allProjectsOpenedWithWorkspace();
+
+ /**
+ * Sent when a project is opened.
+ * @param project the project being opened.
+ */
+ public void projectOpened(IProject project);
+
+ /**
+ * Sent when a project is closed.
+ * @param project the project being closed.
+ */
+ public void projectClosed(IProject project);
+
+ /**
+ * Sent when a project is deleted.
+ * @param project the project about to be deleted.
+ */
+ public void projectDeleted(IProject project);
+
+ /**
+ * Sent when a project is renamed. During a project rename
+ * {@link #projectDeleted(IProject)} and {@link #projectOpened(IProject)} are also called.
+ * This is called last.
+ *
+ * @param project the new {@link IProject} object.
+ * @param from the path of the project before the rename action.
+ */
+ public void projectRenamed(IProject project, IPath from);
+ }
+
+ /**
+ * Classes which implement this interface provide a method that deals
+ * with folder change events
+ */
+ public interface IFolderListener {
+ /**
+ * Sent when a folder changed.
+ * @param folder The file that was changed
+ * @param kind The change kind. This is equivalent to {@link IResourceDelta#getKind()}
+ * @param isAndroidProject whether the parent project is an Android Project
+ */
+ public void folderChanged(IFolder folder, int kind, boolean isAndroidProject);
+ }
+
+ /**
+ * Interface for a listener to be notified when resource change event starts and ends.
+ */
+ public interface IResourceEventListener {
+ public void resourceChangeEventStart();
+ public void resourceChangeEventEnd();
+ }
+
+ /**
+ * 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 {
+ /** Mask value to accept all events */
+ public final static int MASK_NONE = -1;
+
+ /**
+ * Event mask. Values accepted are IResourceDelta.###
+ * @see IResourceDelta#ADDED
+ * @see IResourceDelta#REMOVED
+ * @see IResourceDelta#CHANGED
+ * @see IResourceDelta#ADDED_PHANTOM
+ * @see IResourceDelta#REMOVED_PHANTOM
+ * */
+ int kindMask;
+ }
+
+ /**
+ * Listener bundle for file event.
+ */
+ private static class FileListenerBundle extends ListenerBundle {
+
+ /** The file listener */
+ IFileListener listener;
+ }
+
+ /**
+ * Listener bundle for folder event.
+ */
+ private static class FolderListenerBundle extends ListenerBundle {
+ /** The file listener */
+ IFolderListener listener;
+ }
+
+ private final ArrayList<FileListenerBundle> mFileListeners =
+ new ArrayList<FileListenerBundle>();
+
+ private final ArrayList<FolderListenerBundle> mFolderListeners =
+ new ArrayList<FolderListenerBundle>();
+
+ private final ArrayList<IProjectListener> mProjectListeners = new ArrayList<IProjectListener>();
+
+ private final ArrayList<IResourceEventListener> mEventListeners =
+ new ArrayList<IResourceEventListener>();
+
+ private final ArrayList<IRawDeltaListener> mRawDeltaListeners =
+ new ArrayList<IRawDeltaListener>();
+
+ private IWorkspace mWorkspace;
+
+ private boolean mIsAndroidProject;
+
+ /**
+ * Delta visitor for resource changes.
+ */
+ private final class DeltaVisitor implements IResourceDeltaVisitor {
+
+ @Override
+ public boolean visit(IResourceDelta delta) {
+ // Find the other resource listeners to notify
+ IResource r = delta.getResource();
+ int type = r.getType();
+ if (type == IResource.FILE) {
+ int kind = delta.getKind();
+ // notify the listeners.
+ for (FileListenerBundle bundle : mFileListeners) {
+ if (bundle.kindMask == ListenerBundle.MASK_NONE
+ || (bundle.kindMask & kind) != 0) {
+ try {
+ bundle.listener.fileChanged((IFile)r, delta.getMarkerDeltas(), kind,
+ r.getFileExtension(), delta.getFlags(), mIsAndroidProject);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IFileListener.fileChanged");
+ }
+ }
+ }
+ return false;
+ } else if (type == IResource.FOLDER) {
+ int kind = delta.getKind();
+ // notify the listeners.
+ for (FolderListenerBundle bundle : mFolderListeners) {
+ if (bundle.kindMask == ListenerBundle.MASK_NONE
+ || (bundle.kindMask & kind) != 0) {
+ try {
+ bundle.listener.folderChanged((IFolder)r, kind, mIsAndroidProject);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IFileListener.folderChanged");
+ }
+ }
+ }
+ return true;
+ } else if (type == IResource.PROJECT) {
+ IProject project = (IProject)r;
+
+ try {
+ mIsAndroidProject = project.hasNature(AdtConstants.NATURE_DEFAULT);
+ } catch (CoreException e) {
+ // this can only happen if the project does not exist or is not open, neither
+ // of which can happen here since we are processing changes in the project
+ // or at worst a project post-open event.
+ return false;
+ }
+
+ if (mIsAndroidProject == false) {
+ // for non android project, skip the project listeners but return true
+ // to visit the children and notify the IFileListeners
+ return true;
+ }
+
+ int flags = delta.getFlags();
+
+ if ((flags & IResourceDelta.OPEN) != 0) {
+ // the project is opening or closing.
+
+ if (project.isOpen()) {
+ // notify the listeners.
+ for (IProjectListener pl : mProjectListeners) {
+ try {
+ pl.projectOpened(project);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IProjectListener.projectOpened");
+ }
+ }
+ } else {
+ // notify the listeners.
+ for (IProjectListener pl : mProjectListeners) {
+ try {
+ pl.projectClosed(project);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IProjectListener.projectClosed");
+ }
+ }
+ }
+
+ if ((flags & IResourceDelta.MOVED_FROM) != 0) {
+ IPath from = delta.getMovedFromPath();
+ // notify the listeners.
+ for (IProjectListener pl : mProjectListeners) {
+ try {
+ pl.projectRenamed(project, from);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IProjectListener.projectRenamed");
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ public static GlobalProjectMonitor getMonitor() {
+ return sThis;
+ }
+
+
+ /**
+ * Starts the resource monitoring.
+ * @param ws The current workspace.
+ * @return The monitor object.
+ */
+ public static GlobalProjectMonitor startMonitoring(IWorkspace ws) {
+ if (sThis != null) {
+ ws.addResourceChangeListener(sThis.mResourceChangeListener,
+ IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE);
+ sThis.mWorkspace = ws;
+ }
+ return sThis;
+ }
+
+ /**
+ * Stops the resource monitoring.
+ * @param ws The current workspace.
+ */
+ public static void stopMonitoring(IWorkspace ws) {
+ if (sThis != null) {
+ ws.removeResourceChangeListener(sThis.mResourceChangeListener);
+
+ synchronized (sThis) {
+ sThis.mFileListeners.clear();
+ sThis.mProjectListeners.clear();
+ }
+ }
+ }
+
+ /**
+ * Adds a file listener.
+ * @param listener The listener to receive the events.
+ * @param kindMask The event mask to filter out specific events.
+ * {@link ListenerBundle#MASK_NONE} will forward all events.
+ * See {@link ListenerBundle#kindMask} for more values.
+ */
+ public synchronized void addFileListener(IFileListener listener, int kindMask) {
+ FileListenerBundle bundle = new FileListenerBundle();
+ bundle.listener = listener;
+ bundle.kindMask = kindMask;
+
+ mFileListeners.add(bundle);
+ }
+
+ /**
+ * Removes an existing file listener.
+ * @param listener the listener to remove.
+ */
+ public synchronized void removeFileListener(IFileListener listener) {
+ for (int i = 0 ; i < mFileListeners.size() ; i++) {
+ FileListenerBundle bundle = mFileListeners.get(i);
+ if (bundle.listener == listener) {
+ mFileListeners.remove(i);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Adds a folder listener.
+ * @param listener The listener to receive the events.
+ * @param kindMask The event mask to filter out specific events.
+ * {@link ListenerBundle#MASK_NONE} will forward all events.
+ * See {@link ListenerBundle#kindMask} for more values.
+ */
+ public synchronized void addFolderListener(IFolderListener listener, int kindMask) {
+ FolderListenerBundle bundle = new FolderListenerBundle();
+ bundle.listener = listener;
+ bundle.kindMask = kindMask;
+
+ mFolderListeners.add(bundle);
+ }
+
+ /**
+ * Removes an existing folder listener.
+ * @param listener the listener to remove.
+ */
+ public synchronized void removeFolderListener(IFolderListener listener) {
+ for (int i = 0 ; i < mFolderListeners.size() ; i++) {
+ FolderListenerBundle bundle = mFolderListeners.get(i);
+ if (bundle.listener == listener) {
+ mFolderListeners.remove(i);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Adds a project listener.
+ * @param listener The listener to receive the events.
+ */
+ public synchronized void addProjectListener(IProjectListener listener) {
+ mProjectListeners.add(listener);
+
+ // we need to look at the opened projects and give them to the listener.
+
+ // get the list of opened android projects.
+ IWorkspaceRoot workspaceRoot = mWorkspace.getRoot();
+ IJavaModel javaModel = JavaCore.create(workspaceRoot);
+ IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(javaModel,
+ null /*filter*/);
+
+
+ notifyResourceEventStart();
+
+ for (IJavaProject androidProject : androidProjects) {
+ listener.projectOpenedWithWorkspace(androidProject.getProject());
+ }
+
+ listener.allProjectsOpenedWithWorkspace();
+
+ notifyResourceEventEnd();
+ }
+
+ /**
+ * Removes an existing project listener.
+ * @param listener the listener to remove.
+ */
+ public synchronized void removeProjectListener(IProjectListener listener) {
+ mProjectListeners.remove(listener);
+ }
+
+ /**
+ * Adds a resource event listener.
+ * @param listener The listener to receive the events.
+ */
+ public synchronized void addResourceEventListener(IResourceEventListener listener) {
+ mEventListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing Resource Event listener.
+ * @param listener the listener to remove.
+ */
+ public synchronized void removeResourceEventListener(IResourceEventListener listener) {
+ mEventListeners.remove(listener);
+ }
+
+ /**
+ * 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 void notifyResourceEventStart() {
+ for (IResourceEventListener listener : mEventListeners) {
+ try {
+ listener.resourceChangeEventStart();
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventStart");
+ }
+ }
+ }
+
+ private void notifyResourceEventEnd() {
+ for (IResourceEventListener listener : mEventListeners) {
+ try {
+ listener.resourceChangeEventEnd();
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventEnd");
+ }
+ }
+ }
+
+ private IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {
+ /**
+ * Processes the workspace resource change events.
+ *
+ * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
+ */
+ @Override
+ public synchronized void resourceChanged(IResourceChangeEvent event) {
+ // notify the event listeners of a start.
+ notifyResourceEventStart();
+
+ if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
+ // a project is being deleted. Lets get the project object and remove
+ // its compiled resource list.
+ IResource r = event.getResource();
+ IProject project = r.getProject();
+
+ // notify the listeners.
+ for (IProjectListener pl : mProjectListeners) {
+ try {
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
+ try {
+ pl.projectDeleted(project);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IProjectListener.projectDeleted");
+ }
+ }
+ } catch (CoreException e) {
+ // just ignore this project.
+ }
+ }
+ } else {
+ // this a regular resource change. We get the delta and go through it with a visitor.
+ IResourceDelta delta = event.getDelta();
+
+ // notify the raw delta listeners
+ for (IRawDeltaListener listener : mRawDeltaListeners) {
+ listener.visitDelta(delta);
+ }
+
+ DeltaVisitor visitor = new DeltaVisitor();
+ try {
+ delta.accept(visitor);
+ } catch (CoreException e) {
+ }
+ }
+
+ // we're done, notify the event listeners.
+ notifyResourceEventEnd();
+ }
+ };
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
new file mode 100644
index 000000000..d61324937
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2011 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 static com.android.SdkConstants.ANDROID_URI;
+import static com.android.ide.eclipse.adt.AdtConstants.MARKER_AAPT_COMPILE;
+import static org.eclipse.core.resources.IResource.DEPTH_ONE;
+import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ScanningContext;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.AaptParser;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An {@link IdeScanningContext} is a specialized {@link ScanningContext} which
+ * carries extra information about the scanning state, such as which file is
+ * currently being scanned, and which files have been scanned in the past, such
+ * that at the end of a scan we can mark and clear errors, etc.
+ */
+public class IdeScanningContext extends ScanningContext {
+ private final IProject mProject;
+ private final List<IResource> mScannedResources = new ArrayList<IResource>();
+ private IResource mCurrentFile;
+ private List<Pair<IResource, String>> mErrors;
+ private Set<IProject> mFullAaptProjects;
+ private boolean mValidate;
+ private Map<String, AttributeInfo> mAttributeMap;
+ private ResourceRepository mFrameworkResources;
+
+ /**
+ * Constructs a new {@link IdeScanningContext}
+ *
+ * @param repository the associated {@link ResourceRepository}
+ * @param project the associated project
+ * @param validate if true, check that the attributes and resources are
+ * valid and if not request a full AAPT check
+ */
+ public IdeScanningContext(@NonNull ResourceRepository repository, @NonNull IProject project,
+ boolean validate) {
+ super(repository);
+ mProject = project;
+ mValidate = validate;
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ AndroidTargetData targetData = sdk.getTargetData(project);
+ if (targetData != null) {
+ mAttributeMap = targetData.getAttributeMap();
+ mFrameworkResources = targetData.getFrameworkResources();
+ }
+ }
+ }
+
+ @Override
+ public void addError(@NonNull String error) {
+ super.addError(error);
+
+ if (mErrors == null) {
+ mErrors = new ArrayList<Pair<IResource,String>>();
+ }
+ mErrors.add(Pair.of(mCurrentFile, error));
+ }
+
+ /**
+ * Notifies the context that the given resource is about to be scanned.
+ *
+ * @param resource the resource about to be scanned
+ */
+ public void startScanning(@NonNull IResource resource) {
+ assert mCurrentFile == null : mCurrentFile;
+ mCurrentFile = resource;
+ mScannedResources.add(resource);
+ }
+
+ /**
+ * Notifies the context that the given resource has been scanned.
+ *
+ * @param resource the resource that was scanned
+ */
+ public void finishScanning(@NonNull IResource resource) {
+ assert mCurrentFile != null;
+ mCurrentFile = null;
+ }
+
+ /**
+ * Process any errors found to add error markers in the affected files (and
+ * also clear up any aapt errors in files that are no longer applicable)
+ *
+ * @param async if true, delay updating markers until the next display
+ * thread event loop update
+ */
+ public void updateMarkers(boolean async) {
+ // Run asynchronously? This is necessary for example when adding markers
+ // as the result of a resource change notification, since at that point the
+ // resource tree is locked for modifications and attempting to create a
+ // marker will throw a org.eclipse.core.internal.resources.ResourceException.
+ if (async) {
+ AdtPlugin.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ updateMarkers(false);
+ }
+ });
+ return;
+ }
+
+ // First clear out old/previous markers
+ for (IResource resource : mScannedResources) {
+ try {
+ if (resource.exists()) {
+ int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO;
+ resource.deleteMarkers(MARKER_AAPT_COMPILE, true, depth);
+ }
+ } catch (CoreException ce) {
+ // Pass
+ }
+ }
+
+ // Add new errors
+ if (mErrors != null && mErrors.size() > 0) {
+ List<String> errors = new ArrayList<String>();
+ for (Pair<IResource, String> pair : mErrors) {
+ errors.add(pair.getSecond());
+ }
+ AaptParser.parseOutput(errors, mProject);
+ }
+ }
+
+ @Override
+ public boolean needsFullAapt() {
+ // returns true if it was explicitly requested or if a file that has errors was modified.
+ // This handles the case where an edit doesn't add any new id but fix a compile error.
+ return super.needsFullAapt() || hasModifiedFilesWithErrors();
+ }
+
+ /**
+ * Returns true if any of the scanned resources has an error marker on it.
+ */
+ private boolean hasModifiedFilesWithErrors() {
+ for (IResource resource : mScannedResources) {
+ try {
+ int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO;
+ if (resource.exists()) {
+ IMarker[] markers = resource.findMarkers(IMarker.PROBLEM,
+ true /*includeSubtypes*/, depth);
+ for (IMarker marker : markers) {
+ if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) ==
+ IMarker.SEVERITY_ERROR) {
+ return true;
+ }
+ }
+ }
+ } catch (CoreException ce) {
+ // Pass
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void requestFullAapt() {
+ super.requestFullAapt();
+
+ if (mCurrentFile != null) {
+ if (mFullAaptProjects == null) {
+ mFullAaptProjects = new HashSet<IProject>();
+ }
+ mFullAaptProjects.add(mCurrentFile.getProject());
+ } else {
+ assert false : "No current context to apply IdeScanningContext to";
+ }
+ }
+
+ /**
+ * Returns the collection of projects that scanned resources have requested
+ * a full aapt for.
+ *
+ * @return a collection of projects that scanned resources requested full
+ * aapt runs for, or null
+ */
+ public Collection<IProject> getAaptRequestedProjects() {
+ return mFullAaptProjects;
+ }
+
+ @Override
+ public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) {
+ if (!mValidate) {
+ return true;
+ }
+
+ if (!needsFullAapt() && mAttributeMap != null && ANDROID_URI.equals(uri)) {
+ AttributeInfo info = mAttributeMap.get(name);
+ if (info != null && !info.isValid(value, mRepository, mFrameworkResources)) {
+ return false;
+ }
+ }
+
+ return super.checkValue(uri, name, value);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java
new file mode 100644
index 000000000..e07f09927
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2008 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.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.BuildHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ClassLoader able to load class from output of an Eclipse project.
+ */
+public final class ProjectClassLoader extends ClassLoader {
+
+ private final IJavaProject mJavaProject;
+ private URLClassLoader mJarClassLoader;
+ private boolean mInsideJarClassLoader = false;
+
+ public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) {
+ super(parentClassLoader);
+ mJavaProject = JavaCore.create(project);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ // if we are here through a child classloader, throw an exception.
+ if (mInsideJarClassLoader) {
+ throw new ClassNotFoundException(name);
+ }
+
+ // attempt to load the class from the main project
+ Class<?> clazz = loadFromProject(mJavaProject, name);
+
+ if (clazz != null) {
+ return clazz;
+ }
+
+ // attempt to load the class from the jar dependencies
+ clazz = loadClassFromJar(name);
+ if (clazz != null) {
+ return clazz;
+ }
+
+ // attempt to load the class from the libraries
+ try {
+ // get the project info
+ ProjectState projectState = Sdk.getProjectState(mJavaProject.getProject());
+
+ // this can happen if the project has no project.properties.
+ if (projectState != null) {
+
+ List<IProject> libProjects = projectState.getFullLibraryProjects();
+ List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(
+ libProjects);
+
+ for (IJavaProject javaProject : referencedJavaProjects) {
+ clazz = loadFromProject(javaProject, name);
+
+ if (clazz != null) {
+ return clazz;
+ }
+ }
+ }
+ } catch (CoreException e) {
+ // log exception?
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+
+ /**
+ * Attempts to load a class from a project output folder.
+ * @param project the project to load the class from
+ * @param name the name of the class
+ * @return a class object if found, null otherwise.
+ */
+ private Class<?> loadFromProject(IJavaProject project, String name) {
+ try {
+ // get the project output folder.
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IPath outputLocation = project.getOutputLocation();
+ IResource outRes = root.findMember(outputLocation);
+ if (outRes == null) {
+ return null;
+ }
+
+ File outFolder = new File(outRes.getLocation().toOSString());
+
+ // get the class name segments
+ String[] segments = name.split("\\."); //$NON-NLS-1$
+
+ // try to load the class from the bin folder of the project.
+ File classFile = getFile(outFolder, segments, 0);
+ if (classFile == null) {
+ return null;
+ }
+
+ // load the content of the file and create the class.
+ FileInputStream fis = new FileInputStream(classFile);
+ byte[] data = new byte[(int)classFile.length()];
+ int read = 0;
+ try {
+ read = fis.read(data);
+ } catch (IOException e) {
+ data = null;
+ }
+ fis.close();
+
+ if (data != null) {
+ try {
+ Class<?> clazz = defineClass(null, data, 0, read);
+ if (clazz != null) {
+ return clazz;
+ }
+ } catch (UnsupportedClassVersionError e) {
+ // Attempt to reload on lower version
+ int maxVersion = 50; // JDK 1.6
+ try {
+ byte[] rewritten = rewriteClass(data, maxVersion, 0);
+ return defineClass(null, rewritten, 0, rewritten.length);
+ } catch (UnsupportedClassVersionError e2) {
+ throw e; // throw *original* exception, not attempt to rewrite
+ }
+ }
+ }
+ } catch (Exception e) {
+ // log the exception?
+ }
+
+ return null;
+ }
+
+ /**
+ * Rewrites the given class to the given target class file version.
+ */
+ public static byte[] rewriteClass(byte[] classData, final int maxVersion, final int minVersion) {
+ assert maxVersion >= minVersion;
+ ClassWriter classWriter = new ClassWriter(0);
+ ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (version > maxVersion) {
+ version = maxVersion;
+ }
+ if (version < minVersion) {
+ version = minVersion;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+ };
+ ClassReader reader = new ClassReader(classData);
+ reader.accept(classVisitor, 0);
+ return classWriter.toByteArray();
+ }
+
+ /**
+ * Returns the File matching the a certain path from a root {@link File}.
+ * <p/>The methods checks that the file ends in .class even though the last segment
+ * does not.
+ * @param parent the root of the file.
+ * @param segments the segments containing the path of the file
+ * @param index the offset at which to start looking into segments.
+ * @throws FileNotFoundException
+ */
+ private File getFile(File parent, String[] segments, int index)
+ throws FileNotFoundException {
+ // reached the end with no match?
+ if (index == segments.length) {
+ throw new FileNotFoundException();
+ }
+
+ String toMatch = segments[index];
+ File[] files = parent.listFiles();
+
+ // we're at the last segments. we look for a matching <file>.class
+ if (index == segments.length - 1) {
+ toMatch = toMatch + ".class";
+
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile() && file.getName().equals(toMatch)) {
+ return file;
+ }
+ }
+ }
+
+ // no match? abort.
+ throw new FileNotFoundException();
+ }
+
+ String innerClassName = null;
+
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ if (toMatch.equals(file.getName())) {
+ return getFile(file, segments, index+1);
+ }
+ } else if (file.getName().startsWith(toMatch)) {
+ if (innerClassName == null) {
+ StringBuilder sb = new StringBuilder(segments[index]);
+ for (int i = index + 1 ; i < segments.length ; i++) {
+ sb.append('$');
+ sb.append(segments[i]);
+ }
+ sb.append(".class");
+
+ innerClassName = sb.toString();
+ }
+
+ if (file.getName().equals(innerClassName)) {
+ return file;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads a class from the 3rd party jar present in the project
+ *
+ * @return the class loader or null if not found.
+ */
+ private Class<?> loadClassFromJar(String name) {
+ if (mJarClassLoader == null) {
+ // get the OS path to all the external jars
+ URL[] jars = getExternalJars();
+
+ mJarClassLoader = new URLClassLoader(jars, this /* parent */);
+ }
+
+ try {
+ // because a class loader always look in its parent loader first, we need to know
+ // that we are querying the jar classloader. This will let us know to not query
+ // it again for classes we don't find, or this would create an infinite loop.
+ mInsideJarClassLoader = true;
+ return mJarClassLoader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ // not found? return null.
+ return null;
+ } finally {
+ mInsideJarClassLoader = false;
+ }
+ }
+
+ /**
+ * Returns an array of external jar files used by the project.
+ * @return an array of OS-specific absolute file paths
+ */
+ private final URL[] getExternalJars() {
+ // get a java project from it
+ IJavaProject javaProject = JavaCore.create(mJavaProject.getProject());
+
+ ArrayList<URL> oslibraryList = new ArrayList<URL>();
+ IClasspathEntry[] classpaths = javaProject.readRawClasspath();
+ if (classpaths != null) {
+ for (IClasspathEntry e : classpaths) {
+ if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
+ e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
+ // if this is a classpath variable reference, we resolve it.
+ if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
+ e = JavaCore.getResolvedClasspathEntry(e);
+ }
+
+ handleClassPathEntry(e, oslibraryList);
+ } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+ // get the container.
+ try {
+ IClasspathContainer container = JavaCore.getClasspathContainer(
+ e.getPath(), javaProject);
+ // ignore the system and default_system types as they represent
+ // libraries that are part of the runtime.
+ if (container != null &&
+ container.getKind() == IClasspathContainer.K_APPLICATION) {
+ IClasspathEntry[] entries = container.getClasspathEntries();
+ for (IClasspathEntry entry : entries) {
+ // TODO: Xav -- is this necessary?
+ if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
+ entry = JavaCore.getResolvedClasspathEntry(entry);
+ }
+
+ handleClassPathEntry(entry, oslibraryList);
+ }
+ }
+ } catch (JavaModelException jme) {
+ // can't resolve the container? ignore it.
+ AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s",
+ e.getPath());
+ }
+ }
+ }
+ }
+
+ return oslibraryList.toArray(new URL[oslibraryList.size()]);
+ }
+
+ private void handleClassPathEntry(IClasspathEntry e, ArrayList<URL> oslibraryList) {
+ // get the IPath
+ IPath path = e.getPath();
+
+ // check the name ends with .jar
+ if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
+ boolean local = false;
+ IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
+ if (resource != null && resource.exists() &&
+ resource.getType() == IResource.FILE) {
+ local = true;
+ try {
+ oslibraryList.add(new File(resource.getLocation().toOSString())
+ .toURI().toURL());
+ } catch (MalformedURLException mue) {
+ // pass
+ }
+ }
+
+ if (local == false) {
+ // if the jar path doesn't match a workspace resource,
+ // then we get an OSString and check if this links to a valid file.
+ String osFullPath = path.toOSString();
+
+ File f = new File(osFullPath);
+ if (f.exists()) {
+ try {
+ oslibraryList.add(f.toURI().toURL());
+ } catch (MalformedURLException mue) {
+ // pass
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
new file mode 100644
index 000000000..7c3fd4c13
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -0,0 +1,271 @@
+/*
+ * 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.annotations.NonNull;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.IntArrayWrapper;
+import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+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.IFolderWrapper;
+import com.android.io.IAbstractFolder;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Represents the resources of a project.
+ * On top of the regular {@link ResourceRepository} features it provides:
+ *<ul>
+ *<li>configured resources contain the resources coming from the libraries.</li>
+ *<li>resolution to and from resource integer (compiled value in R.java).</li>
+ *<li>handles resource integer for non existing values of type ID. This is used when rendering.</li>
+ *<li>layouts that have no been saved yet. This is handled by generating dynamic IDs
+ * on the fly.</li>
+ *</ul>
+ */
+@SuppressWarnings("deprecation")
+public class ProjectResources extends ResourceRepository {
+ // project resources are defined as 0x7FXX#### where XX is the resource type (layout, drawable,
+ // etc...). Using FF as the type allows for 255 resource types before we get a collision
+ // which should be fine.
+ private final static int DYNAMIC_ID_SEED_START = 0x7fff0000;
+
+ /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */
+ private Map<ResourceType, Map<String, Integer>> mResourceValueMap;
+ /** Map of (id, [name, resType]) for all resources coming from R.java */
+ private Map<Integer, Pair<ResourceType, String>> mResIdValueToNameMap;
+ /** Map of (int[], name) for styleable resources coming from R.java */
+ private Map<IntArrayWrapper, String> mStyleableValueToNameMap;
+
+ private final DynamicIdMap mDynamicIdMap = new DynamicIdMap(DYNAMIC_ID_SEED_START);
+ private final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
+ private final IProject mProject;
+
+ public static ProjectResources create(IProject project) {
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ return new ProjectResources(project, new IFolderWrapper(resFolder));
+ }
+
+ /**
+ * Makes a ProjectResources for a given <var>project</var>.
+ * @param project the project.
+ */
+ private ProjectResources(IProject project, IAbstractFolder resFolder) {
+ super(resFolder, false /*isFrameworkRepository*/);
+ mProject = project;
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration}, this will
+ * include library dependency.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ @Override
+ @NonNull
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
+ @NonNull FolderConfiguration referenceConfig) {
+ ensureInitialized();
+
+ Map<ResourceType, Map<String, ResourceValue>> resultMap =
+ new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
+
+ // if the project contains libraries, we need to add the libraries resources here
+ // so that they are accessible to the layout rendering.
+ if (mProject != null) {
+ ProjectState state = Sdk.getProjectState(mProject);
+ if (state != null) {
+ List<IProject> libraries = state.getFullLibraryProjects();
+
+ ResourceManager resMgr = ResourceManager.getInstance();
+
+ // because aapt put all the library in their order in this array, the first
+ // one will have priority over the 2nd one. So it's better to loop in the inverse
+ // order and fill the map with resources that will be overwritten by higher
+ // priority resources
+ for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
+ IProject library = libraries.get(i);
+
+ ProjectResources libRes = resMgr.getProjectResources(library);
+ if (libRes != null) {
+ // get the library resources, and only the library, not the dependencies
+ // so call doGetConfiguredResources() directly.
+ Map<ResourceType, Map<String, ResourceValue>> libMap =
+ libRes.doGetConfiguredResources(referenceConfig);
+
+ // we don't want to simply replace the whole map, but instead merge the
+ // content of any sub-map
+ for (Entry<ResourceType, Map<String, ResourceValue>> libEntry :
+ libMap.entrySet()) {
+
+ // get the map currently in the result map for this resource type
+ Map<String, ResourceValue> tempMap = resultMap.get(libEntry.getKey());
+ if (tempMap == null) {
+ // since there's no current map for this type, just add the map
+ // directly coming from the library resources
+ resultMap.put(libEntry.getKey(), libEntry.getValue());
+ } else {
+ // already a map for this type. add the resources from the
+ // library, this will override existing value, which is why
+ // we loop in a specific library order.
+ tempMap.putAll(libEntry.getValue());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // now the project resources themselves.
+ Map<ResourceType, Map<String, ResourceValue>> thisProjectMap =
+ doGetConfiguredResources(referenceConfig);
+
+ // now merge the maps.
+ for (Entry<ResourceType, Map<String, ResourceValue>> entry : thisProjectMap.entrySet()) {
+ ResourceType type = entry.getKey();
+ Map<String, ResourceValue> typeMap = resultMap.get(type);
+ if (typeMap == null) {
+ resultMap.put(type, entry.getValue());
+ } else {
+ typeMap.putAll(entry.getValue());
+ }
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * Returns the {@link ResourceFolder} associated with a {@link IFolder}.
+ * @param folder The {@link IFolder} object.
+ * @return the {@link ResourceFolder} or null if it was not found.
+ *
+ * @see ResourceRepository#getResourceFolder(com.android.io.IAbstractFolder)
+ */
+ public ResourceFolder getResourceFolder(IFolder folder) {
+ return getResourceFolder(new IFolderWrapper(folder));
+ }
+
+ /**
+ * Resolves a compiled resource id into the resource name and type
+ * @param id the resource integer id.
+ * @return a {@link Pair} of 2 strings { name, type } or null if the id could not be resolved
+ */
+ public Pair<ResourceType, String> resolveResourceId(int id) {
+ Pair<ResourceType, String> result = null;
+ if (mResIdValueToNameMap != null) {
+ result = mResIdValueToNameMap.get(id);
+ }
+
+ if (result == null) {
+ synchronized (mDynamicIdMap) {
+ result = mDynamicIdMap.resolveId(id);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Resolves a compiled styleable id of type int[] into the styleable name.
+ */
+ public String resolveStyleable(int[] id) {
+ if (mStyleableValueToNameMap != null) {
+ mWrapper.set(id);
+ return mStyleableValueToNameMap.get(mWrapper);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the integer id of a resource given its type and name.
+ * <p/>If the resource is of type {@link ResourceType#ID} and does not exist in the
+ * internal map, then new id values are dynamically generated (and stored so that queries
+ * with the same names will return the same value).
+ */
+ public Integer getResourceId(ResourceType type, String name) {
+ Integer result = null;
+ if (mResourceValueMap != null) {
+ Map<String, Integer> map = mResourceValueMap.get(type);
+ if (map != null) {
+ result = map.get(name);
+ }
+ }
+
+ if (result == null) {
+ synchronized (mDynamicIdMap) {
+ result = mDynamicIdMap.getId(type, name);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Resets the list of dynamic Ids. This list is used by
+ * {@link #getResourceId(String, String)} when the resource query is an ID that doesn't
+ * exist (for example for ID automatically generated in layout files that are not saved yet.)
+ * <p/>This method resets those dynamic ID and must be called whenever the actual list of IDs
+ * change.
+ */
+ public void resetDynamicIds() {
+ synchronized (mDynamicIdMap) {
+ mDynamicIdMap.reset(DYNAMIC_ID_SEED_START);
+ }
+ }
+
+ @Override
+ @NonNull
+ protected ResourceItem createResourceItem(@NonNull String name) {
+ return new ResourceItem(name);
+ }
+
+ /**
+ * Sets compiled resource information.
+ *
+ * @param resIdValueToNameMap a map of compiled resource id to resource name.
+ * The map is acquired by the {@link ProjectResources} object.
+ * @param styleableValueMap a map of (int[], name) for the styleable information. The map is
+ * acquired by the {@link ProjectResources} object.
+ * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}.
+ * The list is acquired by the {@link ProjectResources} object.
+ */
+ void setCompiledResources(Map<Integer, Pair<ResourceType, String>> resIdValueToNameMap,
+ Map<IntArrayWrapper, String> styleableValueMap,
+ Map<ResourceType, Map<String, Integer>> resourceValueMap) {
+ mResourceValueMap = resourceValueMap;
+ mResIdValueToNameMap = resIdValueToNameMap;
+ mStyleableValueToNameMap = styleableValueMap;
+
+ resetDynamicIds();
+ }
+}
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;
+ }
+}