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