1 /*
   2  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.nio.file.*;
  33 import static java.nio.file.StandardWatchEventKind.*;
  34 import static java.nio.file.LinkOption.*;
  35 import java.nio.file.attribute.*;
  36 import java.io.*;
  37 import java.util.*;
  38 
  39 /**
  40  * Example to watch a directory (or tree) for changes to files.
  41  */
  42 
  43 public class WatchDir {
  44 
  45     private final WatchService watcher;
  46     private final Map<WatchKey,Path> keys;
  47     private final boolean recursive;
  48     private boolean trace = false;
  49 
  50     @SuppressWarnings("unchecked")
  51     static <T> WatchEvent<T> cast(WatchEvent<?> event) {
  52         return (WatchEvent<T>)event;
  53     }
  54 
  55     /**
  56      * Register the given directory with the WatchService
  57      */
  58     private void register(Path dir) throws IOException {
  59         WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
  60         if (trace) {
  61             FileRef prev = keys.get(key);
  62             if (prev == null) {
  63                 System.out.format("register: %s\n", dir);
  64             } else {
  65                 if (!dir.equals(prev)) {
  66                     System.out.format("update: %s -> %s\n", prev, dir);
  67                 }
  68             }
  69         }
  70         keys.put(key, dir);
  71     }
  72 
  73     /**
  74      * Register the given directory, and all its sub-directories, with the
  75      * WatchService.
  76      */
  77     private void registerAll(final Path start) throws IOException {
  78         // register directory and sub-directories
  79         Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
  80             @Override
  81             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
  82                 throws IOException
  83             {
  84                 register(dir);
  85                 return FileVisitResult.CONTINUE;
  86             }
  87         });
  88     }
  89 
  90     /**
  91      * Creates a WatchService and registers the given directory
  92      */
  93     WatchDir(Path dir, boolean recursive) throws IOException {
  94         this.watcher = FileSystems.getDefault().newWatchService();
  95         this.keys = new HashMap<WatchKey,Path>();
  96         this.recursive = recursive;
  97 
  98         if (recursive) {
  99             System.out.format("Scanning %s ...\n", dir);
 100             registerAll(dir);
 101             System.out.println("Done.");
 102         } else {
 103             register(dir);
 104         }
 105 
 106         // enable trace after initial registration
 107         this.trace = true;
 108     }
 109 
 110     /**
 111      * Process all events for keys queued to the watcher
 112      */
 113     void processEvents() {
 114         for (;;) {
 115 
 116             // wait for key to be signalled
 117             WatchKey key;
 118             try {
 119                 key = watcher.take();
 120             } catch (InterruptedException x) {
 121                 return;
 122             }
 123 
 124             Path dir = keys.get(key);
 125             if (dir == null) {
 126                 System.err.println("WatchKey not recognized!!");
 127                 continue;
 128             }
 129 
 130             for (WatchEvent<?> event: key.pollEvents()) {
 131                 WatchEvent.Kind kind = event.kind();
 132 
 133                 // TBD - provide example of how OVERFLOW event is handled
 134                 if (kind == OVERFLOW) {
 135                     continue;
 136                 }
 137 
 138                 // Context for directory entry event is the file name of entry
 139                 WatchEvent<Path> ev = cast(event);
 140                 Path name = ev.context();
 141                 Path child = dir.resolve(name);
 142 
 143                 // print out event
 144                 System.out.format("%s: %s\n", event.kind().name(), child);
 145 
 146                 // if directory is created, and watching recursively, then
 147                 // register it and its sub-directories
 148                 if (recursive && (kind == ENTRY_CREATE)) {
 149                     try {
 150                         if (Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).isDirectory()) {
 151                             registerAll(child);
 152                         }
 153                     } catch (IOException x) {
 154                         // ignore to keep sample readbale
 155                     }
 156                 }
 157             }
 158 
 159             // reset key and remove from set if directory no longer accessible
 160             boolean valid = key.reset();
 161             if (!valid) {
 162                 keys.remove(key);
 163 
 164                 // all directories are inaccessible
 165                 if (keys.isEmpty()) {
 166                     break;
 167                 }
 168             }
 169         }
 170     }
 171 
 172     static void usage() {
 173         System.err.println("usage: java WatchDir [-r] dir");
 174         System.exit(-1);
 175     }
 176 
 177     public static void main(String[] args) throws IOException {
 178         // parse arguments
 179         if (args.length == 0 || args.length > 2)
 180             usage();
 181         boolean recursive = false;
 182         int dirArg = 0;
 183         if (args[0].equals("-r")) {
 184             if (args.length < 2)
 185                 usage();
 186             recursive = true;
 187             dirArg++;
 188         }
 189 
 190         // register directory and process its events
 191         Path dir = Paths.get(args[dirArg]);
 192         new WatchDir(dir, recursive).processEvents();
 193     }
 194 }