1e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera/*
2e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
3e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera *
5e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * This code is free software; you can redistribute it and/or modify it
6e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * under the terms of the GNU General Public License version 2 only, as
7e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * published by the Free Software Foundation.  Oracle designates this
8e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * particular file as subject to the "Classpath" exception as provided
9e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * by Oracle in the LICENSE file that accompanied this code.
10e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera *
11e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * This code is distributed in the hope that it will be useful, but WITHOUT
12e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * version 2 for more details (a copy is included in the LICENSE file that
15e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * accompanied this code).
16e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera *
17e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * You should have received a copy of the GNU General Public License version
18e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * 2 along with this work; if not, write to the Free Software Foundation,
19e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera *
21e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * or visit www.oracle.com if you need additional information or have any
23e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * questions.
24e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera */
25e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
26e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmerapackage sun.nio.fs;
27e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
28e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.nio.file.*;
29e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.nio.file.attribute.*;
30e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.security.AccessController;
31e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.security.PrivilegedAction;
32e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.security.PrivilegedExceptionAction;
33e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.security.PrivilegedActionException;
34e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.io.IOException;
35e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.util.*;
36e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport java.util.concurrent.*;
37e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraimport com.sun.nio.file.SensitivityWatchEventModifier;
38e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
39e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera/**
40e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * Simple WatchService implementation that uses periodic tasks to poll
41e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * registered directories for changes.  This implementation is for use on
42e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera * operating systems that do not have native file change notification support.
43e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera */
44e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
45e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmeraclass PollingWatchService
46e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    extends AbstractWatchService
47e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera{
48e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    // map of registrations
49e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    private final Map<Object,PollingWatchKey> map =
50e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        new HashMap<Object,PollingWatchKey>();
51e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
52e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    // used to execute the periodic tasks that poll for changes
53e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    private final ScheduledExecutorService scheduledExecutor;
54e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
55e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    PollingWatchService() {
56e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // TBD: Make the number of threads configurable
57e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        scheduledExecutor = Executors
58e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            .newSingleThreadScheduledExecutor(new ThreadFactory() {
59e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                 @Override
60e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                 public Thread newThread(Runnable r) {
61e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                     Thread t = new Thread(r);
62e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                     t.setDaemon(true);
63e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                     return t;
64e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                 }});
65e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
66e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
67e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    /**
68e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     * Register the given file with this watch service
69e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     */
70e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    @Override
71e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    WatchKey register(final Path path,
72e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                      WatchEvent.Kind<?>[] events,
73e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                      WatchEvent.Modifier... modifiers)
74e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera         throws IOException
75e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    {
76e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // check events - CCE will be thrown if there are invalid elements
77e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        final Set<WatchEvent.Kind<?>> eventSet =
78e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            new HashSet<WatchEvent.Kind<?>>(events.length);
79e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        for (WatchEvent.Kind<?> event: events) {
80e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // standard events
81e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (event == StandardWatchEventKinds.ENTRY_CREATE ||
82e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                event == StandardWatchEventKinds.ENTRY_MODIFY ||
83e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                event == StandardWatchEventKinds.ENTRY_DELETE)
84e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            {
85e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                eventSet.add(event);
86e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                continue;
87e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
88e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
89e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // OVERFLOW is ignored
90e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (event == StandardWatchEventKinds.OVERFLOW) {
91e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                continue;
92e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
93e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
94e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // null/unsupported
95e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (event == null)
96e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                throw new NullPointerException("An element in event set is 'null'");
97e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new UnsupportedOperationException(event.name());
98e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
99e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        if (eventSet.isEmpty())
100e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new IllegalArgumentException("No events to register");
101e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
102e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // A modifier may be used to specify the sensitivity level
103e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        SensitivityWatchEventModifier sensivity = SensitivityWatchEventModifier.MEDIUM;
104e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        if (modifiers.length > 0) {
105e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            for (WatchEvent.Modifier modifier: modifiers) {
106e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                if (modifier == null)
107e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    throw new NullPointerException();
108e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                if (modifier instanceof SensitivityWatchEventModifier) {
109e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    sensivity = (SensitivityWatchEventModifier)modifier;
110e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    continue;
111e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
112e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                throw new UnsupportedOperationException("Modifier not supported");
113e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
114e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
115e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
116e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // check if watch service is closed
117e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        if (!isOpen())
118e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new ClosedWatchServiceException();
119e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
120e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // registration is done in privileged block as it requires the
121e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // attributes of the entries in the directory.
122e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        try {
123e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            final SensitivityWatchEventModifier s = sensivity;
124e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return AccessController.doPrivileged(
125e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                new PrivilegedExceptionAction<PollingWatchKey>() {
126e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    @Override
127e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    public PollingWatchKey run() throws IOException {
128e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        return doPrivilegedRegister(path, eventSet, s);
129e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    }
130e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                });
131e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        } catch (PrivilegedActionException pae) {
132e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            Throwable cause = pae.getCause();
133e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (cause != null && cause instanceof IOException)
134e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                throw (IOException)cause;
135e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new AssertionError(pae);
136e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
137e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
138e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
139e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    // registers directory returning a new key if not already registered or
140e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    // existing key if already registered
141e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    private PollingWatchKey doPrivilegedRegister(Path path,
142e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                                                 Set<? extends WatchEvent.Kind<?>> events,
143e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                                                 SensitivityWatchEventModifier sensivity)
144e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        throws IOException
145e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    {
146e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // check file is a directory and get its file key if possible
147e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
148e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        if (!attrs.isDirectory()) {
149e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new NotDirectoryException(path.toString());
150e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
151e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        Object fileKey = attrs.fileKey();
152e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        if (fileKey == null)
153e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throw new AssertionError("File keys must be supported");
154e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
155e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // grab close lock to ensure that watch service cannot be closed
156e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        synchronized (closeLock()) {
157e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (!isOpen())
158e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                throw new ClosedWatchServiceException();
159e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
160e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            PollingWatchKey watchKey;
161e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            synchronized (map) {
162e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                watchKey = map.get(fileKey);
163e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                if (watchKey == null) {
164e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // new registration
165e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    watchKey = new PollingWatchKey(path, this, fileKey);
166e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    map.put(fileKey, watchKey);
167e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                } else {
168e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // update to existing registration
169e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    watchKey.disable();
170e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
171e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
172e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            watchKey.enable(events, sensivity.sensitivityValueInSeconds());
173e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return watchKey;
174e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
175e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
176e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
177e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
178e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    @Override
179e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    void implClose() throws IOException {
180e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        synchronized (map) {
181e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            for (Map.Entry<Object,PollingWatchKey> entry: map.entrySet()) {
182e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                PollingWatchKey watchKey = entry.getValue();
183e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                watchKey.disable();
184e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                watchKey.invalidate();
185e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
186e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            map.clear();
187e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
188e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        AccessController.doPrivileged(new PrivilegedAction<Void>() {
189e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            @Override
190e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            public Void run() {
191e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                scheduledExecutor.shutdown();
192e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                return null;
193e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
194e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera         });
195e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
196e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
197e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    /**
198e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     * Entry in directory cache to record file last-modified-time and tick-count
199e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     */
200e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    private static class CacheEntry {
201e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private long lastModified;
202e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private int lastTickCount;
203e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
204e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        CacheEntry(long lastModified, int lastTickCount) {
205e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.lastModified = lastModified;
206e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.lastTickCount = lastTickCount;
207e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
208e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
209e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        int lastTickCount() {
210e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return lastTickCount;
211e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
212e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
213e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        long lastModified() {
214e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return lastModified;
215e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
216e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
217e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        void update(long lastModified, int tickCount) {
218e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.lastModified = lastModified;
219e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.lastTickCount = tickCount;
220e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
221e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
222e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
223e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    /**
224e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     * WatchKey implementation that encapsulates a map of the entries of the
225e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     * entries in the directory. Polling the key causes it to re-scan the
226e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     * directory and queue keys when entries are added, modified, or deleted.
227e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera     */
228e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    private class PollingWatchKey extends AbstractWatchKey {
229e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private final Object fileKey;
230e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
231e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // current event set
232e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private Set<? extends WatchEvent.Kind<?>> events;
233e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
234e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // the result of the periodic task that causes this key to be polled
235e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private ScheduledFuture<?> poller;
236e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
237e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // indicates if the key is valid
238e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private volatile boolean valid;
239e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
240e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // used to detect files that have been deleted
241e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private int tickCount;
242e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
243e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // map of entries in directory
244e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        private Map<Path,CacheEntry> entries;
245e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
246e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        PollingWatchKey(Path dir, PollingWatchService watcher, Object fileKey)
247e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            throws IOException
248e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        {
249e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            super(dir, watcher);
250e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.fileKey = fileKey;
251e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.valid = true;
252e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.tickCount = 0;
253e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            this.entries = new HashMap<Path,CacheEntry>();
254e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
255e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // get the initial entries in the directory
256e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
257e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                for (Path entry: stream) {
258e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // don't follow links
259e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    long lastModified =
260e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
261e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    entries.put(entry.getFileName(), new CacheEntry(lastModified, tickCount));
262e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
263e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            } catch (DirectoryIteratorException e) {
264e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                throw e.getCause();
265e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
266e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
267e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
268e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        Object fileKey() {
269e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return fileKey;
270e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
271e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
272e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        @Override
273e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        public boolean isValid() {
274e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            return valid;
275e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
276e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
277e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        void invalidate() {
278e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            valid = false;
279e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
280e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
281e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // enables periodic polling
282e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {
283e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            synchronized (this) {
284e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // update the events
285e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                this.events = events;
286e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
287e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // create the periodic task
288e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                Runnable thunk = new Runnable() { public void run() { poll(); }};
289e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                this.poller = scheduledExecutor
290e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    .scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);
291e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
292e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
293e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
294e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        // disables periodic polling
295e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        void disable() {
296e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            synchronized (this) {
297e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                if (poller != null)
298e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    poller.cancel(false);
299e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
300e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
301e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
302e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        @Override
303e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        public void cancel() {
304e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            valid = false;
305e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            synchronized (map) {
306e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                map.remove(fileKey());
307e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
308e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            disable();
309e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
310e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
311e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        /**
312e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera         * Polls the directory to detect for new files, modified files, or
313e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera         * deleted files.
314e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera         */
315e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        synchronized void poll() {
316e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            if (!valid) {
317e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                return;
318e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
319e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
320e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // update tick
321e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            tickCount++;
322e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
323e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // open directory
324e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            DirectoryStream<Path> stream = null;
325e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            try {
326e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                stream = Files.newDirectoryStream(watchable());
327e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            } catch (IOException x) {
328e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // directory is no longer accessible so cancel key
329e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                cancel();
330e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                signal();
331e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                return;
332e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
333e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
334e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // iterate over all entries in directory
335e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            try {
336e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                for (Path entry: stream) {
337e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    long lastModified = 0L;
338e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    try {
339e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        lastModified =
340e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
341e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    } catch (IOException x) {
342e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        // unable to get attributes of entry. If file has just
343e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        // been deleted then we'll report it as deleted on the
344e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        // next poll
345e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        continue;
346e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    }
347e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
348e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // lookup cache
349e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    CacheEntry e = entries.get(entry.getFileName());
350e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    if (e == null) {
351e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        // new file found
352e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        entries.put(entry.getFileName(),
353e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                                     new CacheEntry(lastModified, tickCount));
354e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
355e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        // queue ENTRY_CREATE if event enabled
356e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        if (events.contains(StandardWatchEventKinds.ENTRY_CREATE)) {
357e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            signalEvent(StandardWatchEventKinds.ENTRY_CREATE, entry.getFileName());
358e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            continue;
359e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        } else {
360e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            // if ENTRY_CREATE is not enabled and ENTRY_MODIFY is
361e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            // enabled then queue event to avoid missing out on
362e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            // modifications to the file immediately after it is
363e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            // created.
364e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
365e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                                signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, entry.getFileName());
366e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            }
367e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        }
368e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        continue;
369e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    }
370e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
371e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // check if file has changed
372e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    if (e.lastModified != lastModified) {
373e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
374e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                            signalEvent(StandardWatchEventKinds.ENTRY_MODIFY,
375e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                                        entry.getFileName());
376e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        }
377e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    }
378e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // entry in cache so update poll time
379e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    e.update(lastModified, tickCount);
380e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
381e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
382e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            } catch (DirectoryIteratorException e) {
383e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // ignore for now; if the directory is no longer accessible
384e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // then the key will be cancelled on the next poll
385e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            } finally {
386e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
387e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                // close directory stream
388e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                try {
389e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    stream.close();
390e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                } catch (IOException x) {
391e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // ignore
392e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
393e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
394e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera
395e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            // iterate over cache to detect entries that have been deleted
396e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            Iterator<Map.Entry<Path,CacheEntry>> i = entries.entrySet().iterator();
397e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            while (i.hasNext()) {
398e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                Map.Entry<Path,CacheEntry> mapEntry = i.next();
399e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                CacheEntry entry = mapEntry.getValue();
400e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                if (entry.lastTickCount() != tickCount) {
401e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    Path name = mapEntry.getKey();
402e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    // remove from map and queue delete event (if enabled)
403e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    i.remove();
404e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    if (events.contains(StandardWatchEventKinds.ENTRY_DELETE)) {
405e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                        signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
406e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                    }
407e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera                }
408e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera            }
409e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera        }
410e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera    }
411e6bac4bf9c85c2454ce22c91da6c654552c268e0Shubham Ajmera}
412