• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.resources.manager;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
21 
22 import org.eclipse.core.resources.IFile;
23 import org.eclipse.core.resources.IFolder;
24 import org.eclipse.core.resources.IMarkerDelta;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IResource;
27 import org.eclipse.core.resources.IResourceChangeEvent;
28 import org.eclipse.core.resources.IResourceChangeListener;
29 import org.eclipse.core.resources.IResourceDelta;
30 import org.eclipse.core.resources.IResourceDeltaVisitor;
31 import org.eclipse.core.resources.IWorkspace;
32 import org.eclipse.core.resources.IWorkspaceRoot;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IPath;
35 import org.eclipse.jdt.core.IJavaModel;
36 import org.eclipse.jdt.core.IJavaProject;
37 import org.eclipse.jdt.core.JavaCore;
38 
39 import java.util.ArrayList;
40 
41 /**
42  * The Global Project Monitor tracks project file changes, and forward them to simple project,
43  * file, and folder listeners.
44  * Those listeners can be setup with masks to listen to particular events.
45  * <p/>
46  * To track project resource changes, use the monitor in the {@link ResourceManager}. It is more
47  * efficient and while the global ProjectMonitor can track any file, deleted resource files
48  * cannot be matched to previous {@link ResourceFile} or {@link ResourceFolder} objects by the
49  * time the listeners get the event notifications.
50  *
51  * @see IProjectListener
52  * @see IFolderListener
53  * @see IFileListener
54  */
55 public final class GlobalProjectMonitor {
56 
57     private final static GlobalProjectMonitor sThis = new GlobalProjectMonitor();
58 
59     /**
60      * Classes which implement this interface provide a method that deals
61      * with file change events.
62      */
63     public interface IFileListener {
64         /**
65          * Sent when a file changed.
66          * @param file The file that changed.
67          * @param markerDeltas The marker deltas for the file.
68          * @param kind The change kind. This is equivalent to
69          * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
70          */
fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind)71         public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind);
72     }
73 
74     /**
75      * Classes which implements this interface provide methods dealing with project events.
76      */
77     public interface IProjectListener {
78         /**
79          * Sent for each opened android project at the time the listener is put in place.
80          * @param project the opened project.
81          */
projectOpenedWithWorkspace(IProject project)82         public void projectOpenedWithWorkspace(IProject project);
83         /**
84          * Sent when a project is opened.
85          * @param project the project being opened.
86          */
projectOpened(IProject project)87         public void projectOpened(IProject project);
88         /**
89          * Sent when a project is closed.
90          * @param project the project being closed.
91          */
projectClosed(IProject project)92         public void projectClosed(IProject project);
93         /**
94          * Sent when a project is deleted.
95          * @param project the project about to be deleted.
96          */
projectDeleted(IProject project)97         public void projectDeleted(IProject project);
98 
99         /**
100          * Sent when a project is renamed. During a project rename
101          * {@link #projectDeleted(IProject)} and {@link #projectOpened(IProject)} are also called.
102          * This is called last.
103          *
104          * @param project the new {@link IProject} object.
105          * @param from the path of the project before the rename action.
106          */
projectRenamed(IProject project, IPath from)107         public void projectRenamed(IProject project, IPath from);
108     }
109 
110     /**
111      * Classes which implement this interface provide a method that deals
112      * with folder change events
113      */
114     public interface IFolderListener {
115         /**
116          * Sent when a folder changed.
117          * @param folder The file that was changed
118          * @param kind The change kind. This is equivalent to {@link IResourceDelta#getKind()}
119          */
folderChanged(IFolder folder, int kind)120         public void folderChanged(IFolder folder, int kind);
121     }
122 
123     /**
124      * Interface for a listener to be notified when resource change event starts and ends.
125      */
126     public interface IResourceEventListener {
resourceChangeEventStart()127         public void resourceChangeEventStart();
resourceChangeEventEnd()128         public void resourceChangeEventEnd();
129     }
130 
131     /**
132      * Base listener bundle to associate a listener to an event mask.
133      */
134     private static class ListenerBundle {
135         /** Mask value to accept all events */
136         public final static int MASK_NONE = -1;
137 
138         /**
139          * Event mask. Values accepted are IResourceDelta.###
140          * @see IResourceDelta#ADDED
141          * @see IResourceDelta#REMOVED
142          * @see IResourceDelta#CHANGED
143          * @see IResourceDelta#ADDED_PHANTOM
144          * @see IResourceDelta#REMOVED_PHANTOM
145          * */
146         int kindMask;
147     }
148 
149     /**
150      * Listener bundle for file event.
151      */
152     private static class FileListenerBundle extends ListenerBundle {
153 
154         /** The file listener */
155         IFileListener listener;
156     }
157 
158     /**
159      * Listener bundle for folder event.
160      */
161     private static class FolderListenerBundle extends ListenerBundle {
162         /** The file listener */
163         IFolderListener listener;
164     }
165 
166     private final ArrayList<FileListenerBundle> mFileListeners =
167         new ArrayList<FileListenerBundle>();
168 
169     private final ArrayList<FolderListenerBundle> mFolderListeners =
170         new ArrayList<FolderListenerBundle>();
171 
172     private final ArrayList<IProjectListener> mProjectListeners = new ArrayList<IProjectListener>();
173 
174     private final ArrayList<IResourceEventListener> mEventListeners =
175         new ArrayList<IResourceEventListener>();
176 
177     private IWorkspace mWorkspace;
178 
179     /**
180      * Delta visitor for resource changes.
181      */
182     private final class DeltaVisitor implements IResourceDeltaVisitor {
183 
visit(IResourceDelta delta)184         public boolean visit(IResourceDelta delta) {
185             IResource r = delta.getResource();
186             int type = r.getType();
187             if (type == IResource.FILE) {
188                 int kind = delta.getKind();
189                 // notify the listeners.
190                 for (FileListenerBundle bundle : mFileListeners) {
191                     if (bundle.kindMask == ListenerBundle.MASK_NONE
192                             || (bundle.kindMask & kind) != 0) {
193                         try {
194                             bundle.listener.fileChanged((IFile)r, delta.getMarkerDeltas(), kind);
195                         } catch (Throwable t) {
196                             AdtPlugin.log(t,"Failed to call IFileListener.fileChanged");
197                         }
198                     }
199                 }
200                 return false;
201             } else if (type == IResource.FOLDER) {
202                 int kind = delta.getKind();
203                 // notify the listeners.
204                 for (FolderListenerBundle bundle : mFolderListeners) {
205                     if (bundle.kindMask == ListenerBundle.MASK_NONE
206                             || (bundle.kindMask & kind) != 0) {
207                         try {
208                             bundle.listener.folderChanged((IFolder)r, kind);
209                         } catch (Throwable t) {
210                             AdtPlugin.log(t,"Failed to call IFileListener.folderChanged");
211                         }
212                     }
213                 }
214                 return true;
215             } else if (type == IResource.PROJECT) {
216                 int flags = delta.getFlags();
217 
218                 if ((flags & IResourceDelta.OPEN) != 0) {
219                     // the project is opening or closing.
220                     IProject project = (IProject)r;
221 
222                     if (project.isOpen()) {
223                         // notify the listeners.
224                         for (IProjectListener pl : mProjectListeners) {
225                             try {
226                                 pl.projectOpened(project);
227                             } catch (Throwable t) {
228                                 AdtPlugin.log(t,"Failed to call IProjectListener.projectOpened");
229                             }
230                         }
231                     } else {
232                         // notify the listeners.
233                         for (IProjectListener pl : mProjectListeners) {
234                             try {
235                                 pl.projectClosed(project);
236                             } catch (Throwable t) {
237                                 AdtPlugin.log(t,"Failed to call IProjectListener.projectClosed");
238                             }
239                         }
240                     }
241 
242                     if ((flags & IResourceDelta.MOVED_FROM) != 0) {
243                         IPath from = delta.getMovedFromPath();
244                         // notify the listeners.
245                         for (IProjectListener pl : mProjectListeners) {
246                             try {
247                                 pl.projectRenamed(project, from);
248                             } catch (Throwable t) {
249                                 AdtPlugin.log(t,"Failed to call IProjectListener.projectRenamed");
250                             }
251                         }
252                     }
253                 }
254             }
255 
256             return true;
257         }
258     }
259 
getMonitor()260     public static GlobalProjectMonitor getMonitor() {
261         return sThis;
262     }
263 
264 
265     /**
266      * Starts the resource monitoring.
267      * @param ws The current workspace.
268      * @return The monitor object.
269      */
startMonitoring(IWorkspace ws)270     public static GlobalProjectMonitor startMonitoring(IWorkspace ws) {
271         if (sThis != null) {
272             ws.addResourceChangeListener(sThis.mResourceChangeListener,
273                     IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE);
274             sThis.mWorkspace = ws;
275         }
276         return sThis;
277     }
278 
279     /**
280      * Stops the resource monitoring.
281      * @param ws The current workspace.
282      */
stopMonitoring(IWorkspace ws)283     public static void stopMonitoring(IWorkspace ws) {
284         if (sThis != null) {
285             ws.removeResourceChangeListener(sThis.mResourceChangeListener);
286 
287             synchronized (sThis) {
288                 sThis.mFileListeners.clear();
289                 sThis.mProjectListeners.clear();
290             }
291         }
292     }
293 
294     /**
295      * Adds a file listener.
296      * @param listener The listener to receive the events.
297      * @param kindMask The event mask to filter out specific events.
298      * {@link ListenerBundle#MASK_NONE} will forward all events.
299      * See {@link ListenerBundle#kindMask} for more values.
300      */
addFileListener(IFileListener listener, int kindMask)301     public synchronized void addFileListener(IFileListener listener, int kindMask) {
302         FileListenerBundle bundle = new FileListenerBundle();
303         bundle.listener = listener;
304         bundle.kindMask = kindMask;
305 
306         mFileListeners.add(bundle);
307     }
308 
309     /**
310      * Removes an existing file listener.
311      * @param listener the listener to remove.
312      */
removeFileListener(IFileListener listener)313     public synchronized void removeFileListener(IFileListener listener) {
314         for (int i = 0 ; i < mFileListeners.size() ; i++) {
315             FileListenerBundle bundle = mFileListeners.get(i);
316             if (bundle.listener == listener) {
317                 mFileListeners.remove(i);
318                 return;
319             }
320         }
321     }
322 
323     /**
324      * Adds a folder listener.
325      * @param listener The listener to receive the events.
326      * @param kindMask The event mask to filter out specific events.
327      * {@link ListenerBundle#MASK_NONE} will forward all events.
328      * See {@link ListenerBundle#kindMask} for more values.
329      */
addFolderListener(IFolderListener listener, int kindMask)330     public synchronized void addFolderListener(IFolderListener listener, int kindMask) {
331         FolderListenerBundle bundle = new FolderListenerBundle();
332         bundle.listener = listener;
333         bundle.kindMask = kindMask;
334 
335         mFolderListeners.add(bundle);
336     }
337 
338     /**
339      * Removes an existing folder listener.
340      * @param listener the listener to remove.
341      */
removeFolderListener(IFolderListener listener)342     public synchronized void removeFolderListener(IFolderListener listener) {
343         for (int i = 0 ; i < mFolderListeners.size() ; i++) {
344             FolderListenerBundle bundle = mFolderListeners.get(i);
345             if (bundle.listener == listener) {
346                 mFolderListeners.remove(i);
347                 return;
348             }
349         }
350     }
351 
352     /**
353      * Adds a project listener.
354      * @param listener The listener to receive the events.
355      */
addProjectListener(IProjectListener listener)356     public synchronized void addProjectListener(IProjectListener listener) {
357         mProjectListeners.add(listener);
358 
359         // we need to look at the opened projects and give them to the listener.
360 
361         // get the list of opened android projects.
362         IWorkspaceRoot workspaceRoot = mWorkspace.getRoot();
363         IJavaModel javaModel = JavaCore.create(workspaceRoot);
364         IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(javaModel,
365                 null /*filter*/);
366 
367         for (IJavaProject androidProject : androidProjects) {
368             listener.projectOpenedWithWorkspace(androidProject.getProject());
369         }
370     }
371 
372     /**
373      * Removes an existing project listener.
374      * @param listener the listener to remove.
375      */
removeProjectListener(IProjectListener listener)376     public synchronized void removeProjectListener(IProjectListener listener) {
377         mProjectListeners.remove(listener);
378     }
379 
380     /**
381      * Adds a resource event listener.
382      * @param listener The listener to receive the events.
383      */
addResourceEventListener(IResourceEventListener listener)384     public synchronized void addResourceEventListener(IResourceEventListener listener) {
385         mEventListeners.add(listener);
386     }
387 
388     /**
389      * Removes an existing Resource Event listener.
390      * @param listener the listener to remove.
391      */
removeResourceEventListener(IResourceEventListener listener)392     public synchronized void removeResourceEventListener(IResourceEventListener listener) {
393         mEventListeners.remove(listener);
394     }
395 
396     private IResourceChangeListener mResourceChangeListener = new IResourceChangeListener() {
397         /**
398          * Processes the workspace resource change events.
399          *
400          * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
401          */
402         public synchronized void resourceChanged(IResourceChangeEvent event) {
403             // notify the event listeners of a start.
404             for (IResourceEventListener listener : mEventListeners) {
405                 try {
406                     listener.resourceChangeEventStart();
407                 } catch (Throwable t) {
408                     AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventStart");
409                 }
410             }
411 
412             if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
413                 // a project is being deleted. Lets get the project object and remove
414                 // its compiled resource list.
415                 IResource r = event.getResource();
416                 IProject project = r.getProject();
417 
418                 // notify the listeners.
419                 for (IProjectListener pl : mProjectListeners) {
420                     try {
421                         pl.projectDeleted(project);
422                     } catch (Throwable t) {
423                         AdtPlugin.log(t,"Failed to call IProjectListener.projectDeleted");
424                     }
425                 }
426             } else {
427                 // this a regular resource change. We get the delta and go through it with a visitor.
428                 IResourceDelta delta = event.getDelta();
429 
430                 DeltaVisitor visitor = new DeltaVisitor();
431                 try {
432                     delta.accept(visitor);
433                 } catch (CoreException e) {
434                 }
435             }
436 
437             // we're done, notify the event listeners.
438             for (IResourceEventListener listener : mEventListeners) {
439                 try {
440                     listener.resourceChangeEventEnd();
441                 } catch (Throwable t) {
442                     AdtPlugin.log(t,"Failed to call IResourceEventListener.resourceChangeEventEnd");
443                 }
444             }
445         }
446     };
447 
448 }
449