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         /**
     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