2
* Copyright 2008-2011 Uwe Pachler
4
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6
* This code is free software; you can redistribute it and/or modify it
7
* under the terms of the GNU General Public License version 2 only, as
8
* published by the Free Software Foundation. This particular file is
9
* subject to the "Classpath" exception as provided in the LICENSE file
10
* that accompanied this code.
12
* This code is distributed in the hope that it will be useful, but WITHOUT
13
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15
* version 2 for more details (a copy is included in the LICENSE file that
16
* accompanied this code).
18
* You should have received a copy of the GNU General Public License version
19
* 2 along with this work; if not, write to the Free Software Foundation,
20
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24
package name.pachler.nio.file.impl;
26
import name.pachler.nio.file.Path;
28
import java.io.IOException;
29
import java.util.ArrayList;
30
import java.util.HashMap;
31
import java.util.LinkedList;
32
import java.util.List;
34
import java.util.concurrent.ConcurrentLinkedQueue;
35
import java.util.concurrent.SynchronousQueue;
36
import java.util.concurrent.TimeUnit;
37
import java.util.logging.Level;
38
import java.util.logging.Logger;
40
import name.pachler.nio.file.ClosedWatchServiceException;
41
import name.pachler.nio.file.Path;
42
import name.pachler.nio.file.StandardWatchEventKind;
43
import name.pachler.nio.file.WatchEvent;
44
import name.pachler.nio.file.WatchEvent.Kind;
45
import name.pachler.nio.file.WatchEvent.Modifier;
46
import name.pachler.nio.file.WatchKey;
47
import name.pachler.nio.file.ext.ExtendedWatchEventKind;
48
import static name.pachler.nio.file.impl.Windows.*;
54
public class WindowsPathWatchService extends PathWatchService {
55
private static final int DEFAULT_BUFFER_SIZE = 8192; // 8k by default
57
private static native void initNative();
59
private static volatile int threadCounter = 0;
60
private Map<Path, WatchRecord> pathToWatchRecordMap = new HashMap();
61
private List<WindowsPathWatchThread> startedThreads = new LinkedList<WindowsPathWatchThread>();
62
private native void translateFILE_NOTIFY_INFORMATION(WatchRecord watchRecord, ByteBuffer byteBuffer, int bufferSize);
65
* This routine is called from the translateFILE_NOTIFY_INFORMATION native
66
* method and will add events to the given WatchRecord. Note that
67
* the action value recognizes a special value, -1, that indicates that
68
* the queue overflowed.
73
private void FILE_NOTIFY_INFORMATIONhandler(WatchRecord wr, int action, String fileName){
74
int flags = wr.getFlags();
75
WatchEvent.Kind<Path> kind = null;
77
case FILE_ACTION_RENAMED_NEW_NAME:
78
if(0 != (flags & FLAG_FILTER_ENTRY_RENAME_TO))
80
kind = ExtendedWatchEventKind.ENTRY_RENAME_TO;
83
// intentional fallthrough
84
case FILE_ACTION_ADDED:
85
if(0 != (flags & FLAG_FILTER_ENTRY_CREATE))
86
kind = StandardWatchEventKind.ENTRY_CREATE;
88
case FILE_ACTION_RENAMED_OLD_NAME:
89
if(0 != (flags & FLAG_FILTER_ENTRY_RENAME_FROM))
91
kind = ExtendedWatchEventKind.ENTRY_RENAME_FROM;
94
// intentional fallthrough
95
case FILE_ACTION_REMOVED:
96
if(0 != (flags & FLAG_FILTER_ENTRY_DELETE))
97
kind = StandardWatchEventKind.ENTRY_DELETE;
99
case FILE_ACTION_MODIFIED:
100
if(0 != (flags & FLAG_FILTER_ENTRY_MODIFY))
101
kind = StandardWatchEventKind.ENTRY_MODIFY;
108
WatchEvent<?> e = new PathWatchEvent(kind, new PathImpl(new File(fileName)), 1);
109
addWatchEvent(wr, e);
112
void addWatchEvent(WatchRecord wr, WatchEvent<?> e){
113
if(wr.addWatchEvent(e)){
114
pendingWatchKeys.add(wr);
119
NativeLibLoader.loadLibrary("jpathwatch-native");
123
public WindowsPathWatchService(){
126
private void logLastError() {
127
int lastError = GetLastError();
128
String errorMsg = GetLastError_toString(lastError);
129
String message = "Thread '" + Thread.currentThread().getName() + "': error while reading from watch key: " + errorMsg;
130
Logger.getLogger(getClass().getName()).log(Level.WARNING, message);
133
private void cancelImpl(WatchRecord wr) {
134
if((wr.getFlags() & FLAG_FILTER_KEY_INVALID)!=0)
135
addWatchEvent(wr, new VoidWatchEvent(ExtendedWatchEventKind.KEY_INVALID));
137
CloseHandle(wr.handle);
138
CloseHandle(wr.overlapped.getEventHandle());
140
wr.thread.eventHandleToWatchRecord.remove(wr.overlapped.getEventHandle());
141
pathToWatchRecordMap.remove(wr.getPath());
144
private static long openDirectoryHandle(PathImpl pathImpl) {
145
String file = pathImpl.getFile().getAbsolutePath();
146
int shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
147
int flagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
148
return CreateFile(file, FILE_LIST_DIRECTORY, shareMode, null, OPEN_EXISTING, flagsAndAttributes, 0);
151
private static class WatchRecord extends PathWatchKey{
153
WatchRecord(WindowsPathWatchService pws, Path path, int flags, WindowsPathWatchThread thread){
154
super(pws, path, flags);
155
this.thread = thread;
159
public OVERLAPPED overlapped;
160
public ByteBuffer buffer;
161
private WindowsPathWatchThread thread = null;
163
private int getNotifyFilter() {
164
int flags = getFlags();
165
int notifyFilter = 0;
166
if(0 != (flags & FLAG_FILTER_ENTRY_CREATE | FLAG_FILTER_ENTRY_DELETE | FLAG_FILTER_ENTRY_RENAME_FROM | FLAG_FILTER_ENTRY_RENAME_TO))
167
notifyFilter |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME;
168
if(0 != (flags & FLAG_FILTER_ENTRY_MODIFY))
169
notifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE;
173
private boolean getWatchSubtree(){
174
int flags = getFlags();
175
return 0 != (flags & FLAG_WATCH_SUBTREE);
180
private static class Command {
181
static final int TYPE_ADD_WATCHRECORD = 1;
182
static final int TYPE_SHUTDOWN = 2;
183
static final int TYPE_REMOVE_WATCHRECORD = 3;
184
static final int TYPE_MODIFY_WATCHRECORD = 4;
187
private final WatchRecord wr;
190
private Command(int type, WatchRecord wr) {
193
private Command(int type, WatchRecord wr, int flags) {
199
private int getType() {
203
private WatchRecord getWatchRecord() {
207
private int getFlags(){
213
private CloseableBlockingQueue<PathWatchKey> pendingWatchKeys = new CloseableBlockingQueue<PathWatchKey>();
216
public synchronized PathWatchKey register(Path path, Kind<?>[] kinds, Modifier[] modifiers) throws IOException {
219
pathImpl = (PathImpl)path;
220
}catch(ClassCastException ccx){
221
throw new IllegalArgumentException("the provided Path was not created by the newPath factory method of name.pachler.nio.file.ext.Bootstrapper");
223
if(!pathImpl.getFile().isDirectory())
224
throw new IOException("path "+pathImpl.toString()+" is not a directory"); // JDK 1.7 throws NotDirectoryException in this case, but for now we'll just throw an IOException
226
int flags = makeFlagMask(kinds, modifiers);
228
WatchRecord wr = pathToWatchRecordMap.get(path);
229
boolean commandResult;
231
// no WatchRecord for this path yet
232
long directoryHandle = openDirectoryHandle(pathImpl);
234
if(directoryHandle == INVALID_HANDLE_VALUE){
235
int errorCode = GetLastError();
236
throw new IOException(GetLastError_toString(errorCode));
239
WindowsPathWatchThread currentThread = null;
240
for(WindowsPathWatchThread t : startedThreads){
246
if (currentThread == null)
248
currentThread = new WindowsPathWatchThread();
249
// make this thread a deamon thread so that if users forget to close
250
// this WatchService (which instructs the thread to terminate), it will
251
// at least not prevent the JVM from shutting down (the app would
252
// appear to 'hang' on shutdown otherwise)
253
currentThread.setDaemon(true);
254
currentThread.start();
255
startedThreads.add(currentThread);
258
wr = new WatchRecord(this, pathImpl, flags, currentThread);
259
wr.buffer = new ByteBuffer(DEFAULT_BUFFER_SIZE);
260
wr.overlapped = new OVERLAPPED();
261
wr.overlapped.setEvent(CreateEvent(null, true, false, null));
262
wr.handle = directoryHandle;
264
commandResult = currentThread.executeCommand(new Command(Command.TYPE_ADD_WATCHRECORD, wr));
266
// a WatchRecord already exists for this path, so modify it
267
commandResult = wr.thread.executeCommand(new Command(Command.TYPE_MODIFY_WATCHRECORD, wr, flags));
270
// granted, that's not very inspired, but probably enough for something
271
// that should hardly ever happen anyways.
273
throw new IOException("register() failed, details are in log.");
279
synchronized void cancel(PathWatchKey pathWatchKey) {
280
WatchRecord wr = (WatchRecord)pathWatchKey;
281
wr.thread.executeCommand(new Command(Command.TYPE_REMOVE_WATCHRECORD, wr));
282
// if the this WatchRecord was the last in the thread, terminate the thread
283
if(wr.thread.isEmpty()){
284
wr.thread.executeCommand(new Command(Command.TYPE_SHUTDOWN, wr));
285
startedThreads.remove(wr.thread);
290
public synchronized boolean reset(PathWatchKey pathWatchKey) {
291
WatchRecord wr = (WatchRecord)pathWatchKey;
293
if(wr.hasPendingWatchEvents())
294
pendingWatchKeys.add(wr);
299
public synchronized void close() throws IOException {
300
for (WindowsPathWatchThread thread : this.startedThreads) {
303
pendingWatchKeys.close();
307
public WatchKey poll() throws InterruptedException, ClosedWatchServiceException {
308
return pendingWatchKeys.poll();
312
public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException, ClosedWatchServiceException {
313
return pendingWatchKeys.poll(timeout, unit);
317
public WatchKey take() throws InterruptedException, ClosedWatchServiceException {
318
return pendingWatchKeys.take();
323
public class WindowsPathWatchThread extends Thread {
324
private SynchronousQueue<Boolean> commandResultQueue = new SynchronousQueue<Boolean>();
325
private ConcurrentLinkedQueue<Command> commandQueue = new ConcurrentLinkedQueue<Command>();
326
private Map<Long, WatchRecord> eventHandleToWatchRecord = new HashMap<Long, WatchRecord>();
327
private long signallingEvent = CreateEvent(null, true, false, null);
328
WindowsPathWatchThread()
330
this.setName(getClass().getSimpleName() + '-' + threadCounter++);
333
// request to execute command in watcher thread and wait until it has
334
// finished processing it.
335
private synchronized boolean executeCommand(Command command) {
336
commandQueue.add(command);
338
// tell thread that there is a new command
339
SetEvent(signallingEvent);
341
// wait for thread to finish operation if we need syncing
342
boolean success = false;
343
boolean result = false;
346
result = commandResultQueue.take();
348
}catch(InterruptedException ix){
354
public boolean isFull(){
355
return this.eventHandleToWatchRecord.size() >= MAXIMUM_WAIT_OBJECTS-1; //Allow room for the signaling event handle
358
public boolean isEmpty(){
359
return this.eventHandleToWatchRecord.isEmpty();
362
public synchronized void close() throws IOException {
363
if(this.isAlive() == false || signallingEvent == 0)
364
throw new ClosedWatchServiceException();
365
this.executeCommand(new Command(Command.TYPE_SHUTDOWN, null));
371
long[] handles = new long[eventHandleToWatchRecord.size()+1];
372
handles[0] = signallingEvent;
374
for(long h : eventHandleToWatchRecord.keySet())
375
handles[handleIndex++] = h;
379
result = WaitForMultipleObjects(handles, false, INFINITE);
381
catch(RuntimeException e)
383
String message = "Thread '" + Thread.currentThread().getName() + "': error while calling WaitForMultipleObjects. Exception: " + e;
384
Logger.getLogger(getClass().getName()).log(Level.WARNING, message);
385
boolean done = false;
388
// indicate that we processed the command
389
commandResultQueue.put(false);
391
} catch (InterruptedException ex) {
392
Logger.getLogger(WindowsPathWatchService.class.getName()).log(Level.SEVERE, null, ex);
398
if(result == WAIT_OBJECT_0){
399
ResetEvent(signallingEvent);
401
Command cmd = commandQueue.poll();
402
boolean success = false;
403
WatchRecord watchRecord = null;
406
switch(cmd.getType()){
407
case Command.TYPE_SHUTDOWN:
408
// using a duplicate of the watch record to prevent
409
// modifying the list while iterating over it
410
for(WatchRecord wr : new ArrayList<WatchRecord>(eventHandleToWatchRecord.values()))
413
eventHandleToWatchRecord.clear();
414
CloseHandle(signallingEvent);
418
case Command.TYPE_ADD_WATCHRECORD:{
419
watchRecord = cmd.getWatchRecord();
420
eventHandleToWatchRecord.put(watchRecord.overlapped.getEventHandle(),watchRecord);
421
pathToWatchRecordMap.put(watchRecord.getPath(), watchRecord);
423
success = ReadDirectoryChanges(watchRecord.handle, watchRecord.buffer, watchRecord.getWatchSubtree(), watchRecord.getNotifyFilter(), null, watchRecord.overlapped, null);
425
case Command.TYPE_MODIFY_WATCHRECORD:{
426
// on modification, re-issue read operation for watch record
427
watchRecord = cmd.getWatchRecord();
429
int newFlags = cmd.getFlags();
430
int oldFlags = watchRecord.getFlags();
431
boolean watchSubtreeChanged = ((newFlags ^ oldFlags) & FLAG_WATCH_SUBTREE) != 0;
432
watchRecord.setFlags(newFlags);
434
success = CancelIo(watchRecord.handle);
436
if(success && watchSubtreeChanged){
437
// close and re-open directory watch if recursion options changed
438
success = CloseHandle(watchRecord.handle);
439
addWatchEvent(watchRecord, new VoidWatchEvent(StandardWatchEventKind.OVERFLOW));
441
watchRecord.handle = openDirectoryHandle((PathImpl)watchRecord.getPath());
442
success = watchRecord.handle != INVALID_HANDLE_VALUE;
448
success = ReadDirectoryChanges(watchRecord.handle, watchRecord.buffer, watchRecord.getWatchSubtree(), watchRecord.getNotifyFilter(), null, watchRecord.overlapped, null);
451
case Command.TYPE_REMOVE_WATCHRECORD:{
452
watchRecord = cmd.getWatchRecord();
453
cancelImpl(watchRecord);
457
throw new RuntimeException("unhandled command type");
463
cancelImpl(watchRecord);
465
boolean done = false;
468
// indicate that we processed the command
469
commandResultQueue.put(success);
471
} catch (InterruptedException ex) {
472
Logger.getLogger(WindowsPathWatchService.class.getName()).log(Level.SEVERE, null, ex);
477
} else if (WAIT_OBJECT_0 < result && result < handles.length){
478
int index = result - WAIT_OBJECT_0;
479
long h = handles[index];
480
WatchRecord wr = eventHandleToWatchRecord.get(h);
481
ResetEvent(wr.overlapped.getEventHandle());
483
boolean success = true;
485
int[] numberOfBytesTransferred = {0};
487
success = GetOverlappedResult(wr.overlapped.getEventHandle(), wr.overlapped, numberOfBytesTransferred, true);
491
if(numberOfBytesTransferred[0] != 0)
492
translateFILE_NOTIFY_INFORMATION(wr, wr.buffer, numberOfBytesTransferred[0]);
494
addWatchEvent(wr, new VoidWatchEvent(StandardWatchEventKind.OVERFLOW)); // handle queue overflow
497
// queue another I/O operation
499
success = ReadDirectoryChanges(wr.handle, wr.buffer, wr.getWatchSubtree(), wr.getNotifyFilter(), null, wr.overlapped, null);
501
// finally, if something went wrong, cancel watch key
503
// ERROR_ACCESS_DENIED indicates that we can no longer
504
// read from the watched directory, probably because
505
// it has been deleted or unmounted.
506
// Other errors might indicate a real problem and are
508
if(GetLastError() != ERROR_ACCESS_DENIED)
518
public void finalize() throws Throwable{