| /* |
| * 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(); |
| } |
| }; |
| } |