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