Home | History | Annotate | Download | only in manager
      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