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.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