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