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 * Sent when a project is opened. 87 * @param project the project being opened. 88 */ 89 public void projectOpened(IProject project); 90 /** 91 * Sent when a project is closed. 92 * @param project the project being closed. 93 */ 94 public void projectClosed(IProject project); 95 /** 96 * Sent when a project is deleted. 97 * @param project the project about to be deleted. 98 */ 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 */ 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 */ 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 { 129 public void resourceChangeEventStart(); 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 { 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 426 public synchronized void removeRawDeltaListener(IRawDeltaListener listener) { 427 mRawDeltaListeners.remove(listener); 428 } 429 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 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