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