View Javadoc

    1   //========================================================================
    2   //$Id: Scanner.java 3022 2008-06-18 04:06:37Z janb $
    3   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
    4   //------------------------------------------------------------------------
    5   //Licensed under the Apache License, Version 2.0 (the "License");
    6   //you may not use this file except in compliance with the License.
    7   //You may obtain a copy of the License at
    8   //http://www.apache.org/licenses/LICENSE-2.0
    9   //Unless required by applicable law or agreed to in writing, software
    10  //distributed under the License is distributed on an "AS IS" BASIS,
    11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //See the License for the specific language governing permissions and
    13  //limitations under the License.
    14  //========================================================================
    15
    16
    17  package org.mortbay.util;
    18
    19  import java.io.File;
    20  import java.io.FilenameFilter;
    21  import java.io.IOException;
    22  import java.util.ArrayList;
    23  import java.util.Collections;
    24  import java.util.HashMap;
    25  import java.util.HashSet;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Map;
    29  import java.util.Set;
    30  import java.util.Timer;
    31  import java.util.TimerTask;
    32
    33  import org.mortbay.log.Log;
    34
    35
    36  /**
    37   * Scanner
    38   *
    39   * Utility for scanning a directory for added, removed and changed
    40   * files and reporting these events via registered Listeners.
    41   *
    42   * TODO AbstractLifeCycle
    43   */
    44  public class Scanner
    45  {
    46      private int _scanInterval;
    47      private List _listeners = Collections.synchronizedList(new ArrayList());
    48      private Map _prevScan = new HashMap();
    49      private Map _currentScan = new HashMap();
    50      private FilenameFilter _filter;
    51      private List _scanDirs;
    52      private volatile boolean _running = false;
    53      private boolean _reportExisting = true;
    54      private Timer _timer;
    55      private TimerTask _task;
    56      private boolean _recursive=true;
    57
    58
    59      /**
    60       * Listener
    61       *
    62       * Marker for notifications re file changes.
    63       */
    64      public interface Listener
    65      {
    66      }
    67
    68
    69      public interface DiscreteListener extends Listener
    70      {
    71          public void fileChanged (String filename) throws Exception;
    72          public void fileAdded (String filename) throws Exception;
    73          public void fileRemoved (String filename) throws Exception;
    74      }
    75
    76
    77      public interface BulkListener extends Listener
    78      {
    79          public void filesChanged (List filenames) throws Exception;
    80      }
    81
    82
    83      /**
    84       *
    85       */
    86      public Scanner ()
    87      {
    88      }
    89
    90      /**
    91       * Get the scan interval
    92       * @return interval between scans in seconds
    93       */
    94      public int getScanInterval()
    95      {
    96          return _scanInterval;
    97      }
    98
    99      /**
    100      * Set the scan interval
    101      * @param scanInterval pause between scans in seconds
    102      */
    103     public synchronized void setScanInterval(int scanInterval)
    104     {
    105         this._scanInterval = scanInterval;
    106         schedule();
    107     }
    108
    109     /**
    110      * Set the location of the directory to scan.
    111      * @param dir
    112      * @deprecated use setScanDirs(List dirs) instead
    113      */
    114     public void setScanDir (File dir)
    115     {
    116         _scanDirs = new ArrayList();
    117         _scanDirs.add(dir);
    118     }
    119
    120     /**
    121      * Get the location of the directory to scan
    122      * @return
    123      * @deprecated use getScanDirs() instead
    124      */
    125     public File getScanDir ()
    126     {
    127         return (_scanDirs==null?null:(File)_scanDirs.get(0));
    128     }
    129
    130     public void setScanDirs (List dirs)
    131     {
    132         _scanDirs = dirs;
    133     }
    134
    135     public List getScanDirs ()
    136     {
    137         return _scanDirs;
    138     }
    139
    140     public void setRecursive (boolean recursive)
    141     {
    142         _recursive=recursive;
    143     }
    144
    145     public boolean getRecursive ()
    146     {
    147         return _recursive;
    148     }
    149     /**
    150      * Apply a filter to files found in the scan directory.
    151      * Only files matching the filter will be reported as added/changed/removed.
    152      * @param filter
    153      */
    154     public void setFilenameFilter (FilenameFilter filter)
    155     {
    156         this._filter = filter;
    157     }
    158
    159     /**
    160      * Get any filter applied to files in the scan dir.
    161      * @return
    162      */
    163     public FilenameFilter getFilenameFilter ()
    164     {
    165         return _filter;
    166     }
    167
    168     /**
    169      * Whether or not an initial scan will report all files as being
    170      * added.
    171      * @param reportExisting if true, all files found on initial scan will be
    172      * reported as being added, otherwise not
    173      */
    174     public void setReportExistingFilesOnStartup (boolean reportExisting)
    175     {
    176         this._reportExisting = reportExisting;
    177     }
    178
    179     /**
    180      * Add an added/removed/changed listener
    181      * @param listener
    182      */
    183     public synchronized void addListener (Listener listener)
    184     {
    185         if (listener == null)
    186             return;
    187         _listeners.add(listener);
    188     }
    189
    190
    191
    192     /**
    193      * Remove a registered listener
    194      * @param listener the Listener to be removed
    195      */
    196     public synchronized void removeListener (Listener listener)
    197     {
    198         if (listener == null)
    199             return;
    200         _listeners.remove(listener);
    201     }
    202
    203
    204     /**
    205      * Start the scanning action.
    206      */
    207     public synchronized void start ()
    208     {
    209         if (_running)
    210             return;
    211
    212         _running = true;
    213
    214         if (_reportExisting)
    215         {
    216             // if files exist at startup, report them
    217             scan();
    218         }
    219         else
    220         {
    221             //just register the list of existing files and only report changes
    222             scanFiles();
    223             _prevScan.putAll(_currentScan);
    224         }
    225         schedule();
    226     }
    227
    228     public TimerTask newTimerTask ()
    229     {
    230         return new TimerTask()
    231         {
    232             public void run() { scan(); }
    233         };
    234     }
    235
    236     public Timer newTimer ()
    237     {
    238         return new Timer(true);
    239     }
    240
    241     public void schedule ()
    242     {
    243         if (_running)
    244         {
    245             if (_timer!=null)
    246                 _timer.cancel();
    247             if (_task!=null)
    248                 _task.cancel();
    249             if (getScanInterval() > 0)
    250             {
    251                 _timer = newTimer();
    252                 _task = newTimerTask();
    253                 _timer.schedule(_task, 1000L*getScanInterval(),1000L*getScanInterval());
    254             }
    255         }
    256     }
    257     /**
    258      * Stop the scanning.
    259      */
    260     public synchronized void stop ()
    261     {
    262         if (_running)
    263         {
    264             _running = false;
    265             if (_timer!=null)
    266                 _timer.cancel();
    267             if (_task!=null)
    268                 _task.cancel();
    269             _task=null;
    270             _timer=null;
    271         }
    272     }
    273
    274     /**
    275      * Perform a pass of the scanner and report changes
    276      */
    277     public void scan ()
    278     {
    279         scanFiles();
    280         reportDifferences(_currentScan, _prevScan);
    281         _prevScan.clear();
    282         _prevScan.putAll(_currentScan);
    283     }
    284
    285     /**
    286      * Recursively scan all files in the designated directories.
    287      * @return Map of name of file to last modified time
    288      */
    289     public void scanFiles ()
    290     {
    291         if (_scanDirs==null)
    292             return;
    293
    294         _currentScan.clear();
    295         Iterator itor = _scanDirs.iterator();
    296         while (itor.hasNext())
    297         {
    298             File dir = (File)itor.next();
    299
    300             if ((dir != null) && (dir.exists()))
    301                 scanFile(dir, _currentScan);
    302         }
    303     }
    304
    305
    306     /**
    307      * Report the adds/changes/removes to the registered listeners
    308      *
    309      * @param currentScan the info from the most recent pass
    310      * @param oldScan info from the previous pass
    311      */
    312     public void reportDifferences (Map currentScan, Map oldScan)
    313     {
    314         List bulkChanges = new ArrayList();
    315
    316         Set oldScanKeys = new HashSet(oldScan.keySet());
    317         Iterator itor = currentScan.entrySet().iterator();
    318         while (itor.hasNext())
    319         {
    320             Map.Entry entry = (Map.Entry)itor.next();
    321             if (!oldScanKeys.contains(entry.getKey()))
    322             {
    323                 Log.debug("File added: "+entry.getKey());
    324                 reportAddition ((String)entry.getKey());
    325                 bulkChanges.add(entry.getKey());
    326             }
    327             else if (!oldScan.get(entry.getKey()).equals(entry.getValue()))
    328             {
    329                 Log.debug("File changed: "+entry.getKey());
    330                 reportChange((String)entry.getKey());
    331                 oldScanKeys.remove(entry.getKey());
    332                 bulkChanges.add(entry.getKey());
    333             }
    334             else
    335                 oldScanKeys.remove(entry.getKey());
    336         }
    337
    338         if (!oldScanKeys.isEmpty())
    339         {
    340
    341             Iterator keyItor = oldScanKeys.iterator();
    342             while (keyItor.hasNext())
    343             {
    344                 String filename = (String)keyItor.next();
    345                 Log.debug("File removed: "+filename);
    346                 reportRemoval(filename);
    347                 bulkChanges.add(filename);
    348             }
    349         }
    350
    351         if (!bulkChanges.isEmpty())
    352             reportBulkChanges(bulkChanges);
    353     }
    354
    355
    356     /**
    357      * Get last modified time on a single file or recurse if
    358      * the file is a directory.
    359      * @param f file or directory
    360      * @param scanInfoMap map of filenames to last modified times
    361      */
    362     private void scanFile (File f, Map scanInfoMap)
    363     {
    364         try
    365         {
    366             if (!f.exists())
    367                 return;
    368
    369             if (f.isFile())
    370             {
    371                 if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
    372                 {
    373                     String name = f.getCanonicalPath();
    374                     long lastModified = f.lastModified();
    375                     scanInfoMap.put(name, new Long(lastModified));
    376                 }
    377             }
    378             else if (f.isDirectory() && (_recursive || _scanDirs.contains(f)))
    379             {
    380                 File[] files = f.listFiles();
    381                 for (int i=0;i<files.length;i++)
    382                     scanFile(files[i], scanInfoMap);
    383             }
    384         }
    385         catch (IOException e)
    386         {
    387             Log.warn("Error scanning watched files", e);
    388         }
    389     }
    390
    391     private void warn(Object listener,String filename,Throwable th)
    392     {
    393         Log.warn(th);
    394         Log.warn(listener+" failed on '"+filename);
    395     }
    396
    397     /**
    398      * Report a file addition to the registered FileAddedListeners
    399      * @param filename
    400      */
    401     private void reportAddition (String filename)
    402     {
    403         Iterator itor = _listeners.iterator();
    404         while (itor.hasNext())
    405         {
    406             Object l = itor.next();
    407             try
    408             {
    409                 if (l instanceof DiscreteListener)
    410                     ((DiscreteListener)l).fileAdded(filename);
    411             }
    412             catch (Exception e)
    413             {
    414                 warn(l,filename,e);
    415             }
    416             catch (Error e)
    417             {
    418                 warn(l,filename,e);
    419             }
    420         }
    421     }
    422
    423
    424     /**
    425      * Report a file removal to the FileRemovedListeners
    426      * @param filename
    427      */
    428     private void reportRemoval (String filename)
    429     {
    430         Iterator itor = _listeners.iterator();
    431         while (itor.hasNext())
    432         {
    433             Object l = itor.next();
    434             try
    435             {
    436                 if (l instanceof DiscreteListener)
    437                     ((DiscreteListener)l).fileRemoved(filename);
    438             }
    439             catch (Exception e)
    440             {
    441                 warn(l,filename,e);
    442             }
    443             catch (Error e)
    444             {
    445                 warn(l,filename,e);
    446             }
    447         }
    448     }
    449
    450
    451     /**
    452      * Report a file change to the FileChangedListeners
    453      * @param filename
    454      */
    455     private void reportChange (String filename)
    456     {
    457         Iterator itor = _listeners.iterator();
    458         while (itor.hasNext())
    459         {
    460             Object l = itor.next();
    461             try
    462             {
    463                 if (l instanceof DiscreteListener)
    464                     ((DiscreteListener)l).fileChanged(filename);
    465             }
    466             catch (Exception e)
    467             {
    468                 warn(l,filename,e);
    469             }
    470             catch (Error e)
    471             {
    472                 warn(l,filename,e);
    473             }
    474         }
    475     }
    476
    477     private void reportBulkChanges (List filenames)
    478     {
    479         Iterator itor = _listeners.iterator();
    480         while (itor.hasNext())
    481         {
    482             Object l = itor.next();
    483             try
    484             {
    485                 if (l instanceof BulkListener)
    486                     ((BulkListener)l).filesChanged(filenames);
    487             }
    488             catch (Exception e)
    489             {
    490                 warn(l,filenames.toString(),e);
    491             }
    492             catch (Error e)
    493             {
    494                 warn(l,filenames.toString(),e);
    495             }
    496         }
    497     }
    498
    499 }