413 lines · 15.0 KB · Java
RawDownload
/* * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.nio.fs; import java.nio.file.*;import java.nio.file.attribute.*;import java.security.AccessController;import java.security.PrivilegedAction;import java.security.PrivilegedExceptionAction;import java.security.PrivilegedActionException;import java.io.IOException;import java.util.*;import java.util.concurrent.*;import com.sun.nio.file.SensitivityWatchEventModifier; /** * Simple WatchService implementation that uses periodic tasks to poll * registered directories for changes.  This implementation is for use on * operating systems that do not have native file change notification support. */ class PollingWatchService    extends AbstractWatchService{    // map of registrations    private final Map<Object,PollingWatchKey> map =        new HashMap<Object,PollingWatchKey>();     // used to execute the periodic tasks that poll for changes    private final ScheduledExecutorService scheduledExecutor;     PollingWatchService() {        // TBD: Make the number of threads configurable        scheduledExecutor = Executors            .newSingleThreadScheduledExecutor(new ThreadFactory() {                 @Override                 public Thread newThread(Runnable r) {                     Thread t = new Thread(r);                     t.setDaemon(true);                     return t;                 }});    }     /**     * Register the given file with this watch service     */    @Override    WatchKey register(final Path path,                      WatchEvent.Kind<?>[] events,                      WatchEvent.Modifier... modifiers)         throws IOException    {        // check events - CCE will be thrown if there are invalid elements        if (events.length == 0)            throw new IllegalArgumentException("No events to register");        final Set<WatchEvent.Kind<?>> eventSet =            new HashSet<WatchEvent.Kind<?>>(events.length);        for (WatchEvent.Kind<?> event: events) {            // standard events            if (event == StandardWatchEventKinds.ENTRY_CREATE ||                event == StandardWatchEventKinds.ENTRY_MODIFY ||                event == StandardWatchEventKinds.ENTRY_DELETE)            {                eventSet.add(event);                continue;            }             // OVERFLOW is ignored            if (event == StandardWatchEventKinds.OVERFLOW) {                if (events.length == 1)                    throw new IllegalArgumentException("No events to register");                continue;            }             // null/unsupported            if (event == null)                throw new NullPointerException("An element in event set is 'null'");            throw new UnsupportedOperationException(event.name());        }         // A modifier may be used to specify the sensitivity level        SensitivityWatchEventModifier sensivity = SensitivityWatchEventModifier.MEDIUM;        if (modifiers.length > 0) {            for (WatchEvent.Modifier modifier: modifiers) {                if (modifier == null)                    throw new NullPointerException();                if (modifier instanceof SensitivityWatchEventModifier) {                    sensivity = (SensitivityWatchEventModifier)modifier;                    continue;                }                throw new UnsupportedOperationException("Modifier not supported");            }        }         // check if watch service is closed        if (!isOpen())            throw new ClosedWatchServiceException();         // registration is done in privileged block as it requires the        // attributes of the entries in the directory.        try {            final SensitivityWatchEventModifier s = sensivity;            return AccessController.doPrivileged(                new PrivilegedExceptionAction<PollingWatchKey>() {                    @Override                    public PollingWatchKey run() throws IOException {                        return doPrivilegedRegister(path, eventSet, s);                    }                });        } catch (PrivilegedActionException pae) {            Throwable cause = pae.getCause();            if (cause != null && cause instanceof IOException)                throw (IOException)cause;            throw new AssertionError(pae);        }    }     // registers directory returning a new key if not already registered or    // existing key if already registered    private PollingWatchKey doPrivilegedRegister(Path path,                                                 Set<? extends WatchEvent.Kind<?>> events,                                                 SensitivityWatchEventModifier sensivity)        throws IOException    {        // check file is a directory and get its file key if possible        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);        if (!attrs.isDirectory()) {            throw new NotDirectoryException(path.toString());        }        Object fileKey = attrs.fileKey();        if (fileKey == null)            throw new AssertionError("File keys must be supported");         // grab close lock to ensure that watch service cannot be closed        synchronized (closeLock()) {            if (!isOpen())                throw new ClosedWatchServiceException();             PollingWatchKey watchKey;            synchronized (map) {                watchKey = map.get(fileKey);                if (watchKey == null) {                    // new registration                    watchKey = new PollingWatchKey(path, this, fileKey);                    map.put(fileKey, watchKey);                } else {                    // update to existing registration                    watchKey.disable();                }            }            watchKey.enable(events, sensivity.sensitivityValueInSeconds());            return watchKey;        }     }     @Override    void implClose() throws IOException {        synchronized (map) {            for (Map.Entry<Object,PollingWatchKey> entry: map.entrySet()) {                PollingWatchKey watchKey = entry.getValue();                watchKey.disable();                watchKey.invalidate();            }            map.clear();        }        AccessController.doPrivileged(new PrivilegedAction<Void>() {            @Override            public Void run() {                scheduledExecutor.shutdown();                return null;            }         });    }     /**     * Entry in directory cache to record file last-modified-time and tick-count     */    private static class CacheEntry {        private long lastModified;        private int lastTickCount;         CacheEntry(long lastModified, int lastTickCount) {            this.lastModified = lastModified;            this.lastTickCount = lastTickCount;        }         int lastTickCount() {            return lastTickCount;        }         long lastModified() {            return lastModified;        }         void update(long lastModified, int tickCount) {            this.lastModified = lastModified;            this.lastTickCount = tickCount;        }    }     /**     * WatchKey implementation that encapsulates a map of the entries of the     * entries in the directory. Polling the key causes it to re-scan the     * directory and queue keys when entries are added, modified, or deleted.     */    private class PollingWatchKey extends AbstractWatchKey {        private final Object fileKey;         // current event set        private Set<? extends WatchEvent.Kind<?>> events;         // the result of the periodic task that causes this key to be polled        private ScheduledFuture<?> poller;         // indicates if the key is valid        private volatile boolean valid;         // used to detect files that have been deleted        private int tickCount;         // map of entries in directory        private Map<Path,CacheEntry> entries;         PollingWatchKey(Path dir, PollingWatchService watcher, Object fileKey)            throws IOException        {            super(dir, watcher);            this.fileKey = fileKey;            this.valid = true;            this.tickCount = 0;            this.entries = new HashMap<Path,CacheEntry>();             // get the initial entries in the directory            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {                for (Path entry: stream) {                    // don't follow links                    long lastModified =                        Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();                    entries.put(entry.getFileName(), new CacheEntry(lastModified, tickCount));                }            } catch (DirectoryIteratorException e) {                throw e.getCause();            }        }         Object fileKey() {            return fileKey;        }         @Override        public boolean isValid() {            return valid;        }         void invalidate() {            valid = false;        }         // enables periodic polling        void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {            synchronized (this) {                // update the events                this.events = events;                 // create the periodic task                Runnable thunk = new Runnable() { public void run() { poll(); }};                this.poller = scheduledExecutor                    .scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);            }        }         // disables periodic polling        void disable() {            synchronized (this) {                if (poller != null)                    poller.cancel(false);            }        }         @Override        public void cancel() {            valid = false;            synchronized (map) {                map.remove(fileKey());            }            disable();        }         /**         * Polls the directory to detect for new files, modified files, or         * deleted files.         */        synchronized void poll() {            if (!valid) {                return;            }             // update tick            tickCount++;             // open directory            DirectoryStream<Path> stream = null;            try {                stream = Files.newDirectoryStream(watchable());            } catch (IOException x) {                // directory is no longer accessible so cancel key                cancel();                signal();                return;            }             // iterate over all entries in directory            try {                for (Path entry: stream) {                    long lastModified = 0L;                    try {                        lastModified =                            Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();                    } catch (IOException x) {                        // unable to get attributes of entry. If file has just                        // been deleted then we'll report it as deleted on the                        // next poll                        continue;                    }                     // lookup cache                    CacheEntry e = entries.get(entry.getFileName());                    if (e == null) {                        // new file found                        entries.put(entry.getFileName(),                                     new CacheEntry(lastModified, tickCount));                         // queue ENTRY_CREATE if event enabled                        if (events.contains(StandardWatchEventKinds.ENTRY_CREATE)) {                            signalEvent(StandardWatchEventKinds.ENTRY_CREATE, entry.getFileName());                            continue;                        } else {                            // if ENTRY_CREATE is not enabled and ENTRY_MODIFY is                            // enabled then queue event to avoid missing out on                            // modifications to the file immediately after it is                            // created.                            if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {                                signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, entry.getFileName());                            }                        }                        continue;                    }                     // check if file has changed                    if (e.lastModified != lastModified) {                        if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {                            signalEvent(StandardWatchEventKinds.ENTRY_MODIFY,                                        entry.getFileName());                        }                    }                    // entry in cache so update poll time                    e.update(lastModified, tickCount);                 }            } catch (DirectoryIteratorException e) {                // ignore for now; if the directory is no longer accessible                // then the key will be cancelled on the next poll            } finally {                 // close directory stream                try {                    stream.close();                } catch (IOException x) {                    // ignore                }            }             // iterate over cache to detect entries that have been deleted            Iterator<Map.Entry<Path,CacheEntry>> i = entries.entrySet().iterator();            while (i.hasNext()) {                Map.Entry<Path,CacheEntry> mapEntry = i.next();                CacheEntry entry = mapEntry.getValue();                if (entry.lastTickCount() != tickCount) {                    Path name = mapEntry.getKey();                    // remove from map and queue delete event (if enabled)                    i.remove();                    if (events.contains(StandardWatchEventKinds.ENTRY_DELETE)) {                        signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);                    }                }            }        }    }}