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 */ 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 */ 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 */ 93 public void allProjectsOpenedWithWorkspace(); 94 95 /** 96 * Sent when a project is opened. 97 * @param project the project being opened. 98 */ 99 public void projectOpened(IProject project); 100 101 /** 102 * Sent when a project is closed. 103 * @param project the project being closed. 104 */ 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 */ 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 */ 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 */ 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 { 141 public void resourceChangeEventStart(); 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 { 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 441 public synchronized void removeRawDeltaListener(IRawDeltaListener listener) { 442 mRawDeltaListeners.remove(listener); 443 } 444 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 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