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