/*
 * Copyright (c) 2008, 2014, 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.security.AccessController;
import java.security.PrivilegedAction;
import java.io.IOException;
import java.util.*;

Base implementation of background poller thread used in watch service implementations. A poller thread waits on events from the file system and also services "requests" from clients to register for new events or cancel existing registrations.
/** * Base implementation of background poller thread used in watch service * implementations. A poller thread waits on events from the file system and * also services "requests" from clients to register for new events or cancel * existing registrations. */
abstract class AbstractPoller implements Runnable { // list of requests pending to the poller thread private final LinkedList<Request> requestList; // set to true when shutdown private boolean shutdown; protected AbstractPoller() { this.requestList = new LinkedList<>(); this.shutdown = false; }
Starts the poller thread
/** * Starts the poller thread */
public void start() { final Runnable thisRunnable = this; AccessController.doPrivileged(new PrivilegedAction<>() { @Override public Object run() { Thread thr = new Thread(null, thisRunnable, "FileSystemWatchService", 0, false); thr.setDaemon(true); thr.start(); return null; } }); }
Wakeup poller thread so that it can service pending requests
/** * Wakeup poller thread so that it can service pending requests */
abstract void wakeup() throws IOException;
Executed by poller thread to register directory for changes
/** * Executed by poller thread to register directory for changes */
abstract Object implRegister(Path path, Set<? extends WatchEvent.Kind<?>> events, WatchEvent.Modifier... modifiers);
Executed by poller thread to cancel key
/** * Executed by poller thread to cancel key */
abstract void implCancelKey(WatchKey key);
Executed by poller thread to shutdown and cancel all keys
/** * Executed by poller thread to shutdown and cancel all keys */
abstract void implCloseAll();
Requests, and waits on, poller thread to register given file.
/** * Requests, and waits on, poller thread to register given file. */
final WatchKey register(Path dir, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException { // validate arguments before request to poller if (dir == null) throw new NullPointerException(); Set<WatchEvent.Kind<?>> eventSet = new HashSet<>(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) continue; // null/unsupported if (event == null) throw new NullPointerException("An element in event set is 'null'"); throw new UnsupportedOperationException(event.name()); } if (eventSet.isEmpty()) throw new IllegalArgumentException("No events to register"); return (WatchKey)invoke(RequestType.REGISTER, dir, eventSet, modifiers); }
Cancels, and waits on, poller thread to cancel given key.
/** * Cancels, and waits on, poller thread to cancel given key. */
final void cancel(WatchKey key) { try { invoke(RequestType.CANCEL, key); } catch (IOException x) { // should not happen throw new AssertionError(x.getMessage()); } }
Shutdown poller thread
/** * Shutdown poller thread */
final void close() throws IOException { invoke(RequestType.CLOSE); }
Types of request that the poller thread must handle
/** * Types of request that the poller thread must handle */
private static enum RequestType { REGISTER, CANCEL, CLOSE; }
Encapsulates a request (command) to the poller thread.
/** * Encapsulates a request (command) to the poller thread. */
private static class Request { private final RequestType type; private final Object[] params; private boolean completed = false; private Object result = null; Request(RequestType type, Object... params) { this.type = type; this.params = params; } RequestType type() { return type; } Object[] parameters() { return params; } void release(Object result) { synchronized (this) { this.completed = true; this.result = result; notifyAll(); } }
Await completion of the request. The return value is the result of the request.
/** * Await completion of the request. The return value is the result of * the request. */
Object awaitResult() { boolean interrupted = false; synchronized (this) { while (!completed) { try { wait(); } catch (InterruptedException x) { interrupted = true; } } if (interrupted) Thread.currentThread().interrupt(); return result; } } }
Enqueues request to poller thread and waits for result
/** * Enqueues request to poller thread and waits for result */
private Object invoke(RequestType type, Object... params) throws IOException { // submit request Request req = new Request(type, params); synchronized (requestList) { if (shutdown) { throw new ClosedWatchServiceException(); } requestList.add(req); // wakeup thread wakeup(); } // wait for result Object result = req.awaitResult(); if (result instanceof RuntimeException) throw (RuntimeException)result; if (result instanceof IOException ) throw (IOException)result; return result; }
Invoked by poller thread to process all pending requests
Returns: true if poller thread should shutdown
/** * Invoked by poller thread to process all pending requests * * @return true if poller thread should shutdown */
@SuppressWarnings("unchecked") boolean processRequests() { synchronized (requestList) { Request req; while ((req = requestList.poll()) != null) { // if in process of shutdown then reject request if (shutdown) { req.release(new ClosedWatchServiceException()); continue; } switch (req.type()) { /** * Register directory */ case REGISTER: { Object[] params = req.parameters(); Path path = (Path)params[0]; Set<? extends WatchEvent.Kind<?>> events = (Set<? extends WatchEvent.Kind<?>>)params[1]; WatchEvent.Modifier[] modifiers = (WatchEvent.Modifier[])params[2]; req.release(implRegister(path, events, modifiers)); break; } /** * Cancel existing key */ case CANCEL : { Object[] params = req.parameters(); WatchKey key = (WatchKey)params[0]; implCancelKey(key); req.release(null); break; } /** * Close watch service */ case CLOSE: { implCloseAll(); req.release(null); shutdown = true; break; } default: req.release(new IOException("request not recognized")); } } } return shutdown; } }