aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.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/GlobalProjectMonitor.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java546
1 files changed, 546 insertions, 0 deletions
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();
+ }
+ };
+}