alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (WindowsWatchService.java)

This example Java source code file (WindowsWatchService.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

changes_buffer_size, closehandle, filekey, invalid_handle_value, ioexception, nativebuffer, override, poller, set, util, wakeup_completion_key, windowsexception, windowspath, windowswatchkey, windowswatchservice

The WindowsWatchService.java Java example source code

/*
 * Copyright (c) 2008, 2013, 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.io.IOException;
import java.util.*;
import com.sun.nio.file.ExtendedWatchEventModifier;
import sun.misc.Unsafe;

import static sun.nio.fs.WindowsNativeDispatcher.*;
import static sun.nio.fs.WindowsConstants.*;

/*
 * Win32 implementation of WatchService based on ReadDirectoryChangesW.
 */

class WindowsWatchService
    extends AbstractWatchService
{
    private final static int WAKEUP_COMPLETION_KEY = 0;
    private final Unsafe unsafe = Unsafe.getUnsafe();

    // background thread to service I/O completion port
    private final Poller poller;

    /**
     * Creates an I/O completion port and a daemon thread to service it
     */
    WindowsWatchService(WindowsFileSystem fs) throws IOException {
        // create I/O completion port
        long port = 0L;
        try {
            port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0);
        } catch (WindowsException x) {
            throw new IOException(x.getMessage());
        }

        this.poller = new Poller(fs, this, port);
        this.poller.start();
    }

    @Override
    WatchKey register(Path path,
                      WatchEvent.Kind<?>[] events,
                      WatchEvent.Modifier... modifiers)
         throws IOException
    {
        // delegate to poller
        return poller.register(path, events, modifiers);
    }

    @Override
    void implClose() throws IOException {
        // delegate to poller
        poller.close();
    }

    /**
     * Windows implementation of WatchKey.
     */
    private class WindowsWatchKey extends AbstractWatchKey {
        // file key (used to detect existing registrations)
        private final FileKey fileKey;

        // handle to directory
        private volatile long handle = INVALID_HANDLE_VALUE;

        // interest events
        private Set<? extends WatchEvent.Kind events;

        // subtree
        private boolean watchSubtree;

        // buffer for change events
        private NativeBuffer buffer;

        // pointer to bytes returned (in buffer)
        private long countAddress;

        // pointer to overlapped structure (in buffer)
        private long overlappedAddress;

        // completion key (used to map I/O completion to WatchKey)
        private int completionKey;

        WindowsWatchKey(Path dir,
                        AbstractWatchService watcher,
                        FileKey fileKey)
        {
            super(dir, watcher);
            this.fileKey = fileKey;
        }

        WindowsWatchKey init(long handle,
                             Set<? extends WatchEvent.Kind events,
                             boolean watchSubtree,
                             NativeBuffer buffer,
                             long countAddress,
                             long overlappedAddress,
                             int completionKey)
        {
            this.handle = handle;
            this.events = events;
            this.watchSubtree = watchSubtree;
            this.buffer = buffer;
            this.countAddress = countAddress;
            this.overlappedAddress = overlappedAddress;
            this.completionKey = completionKey;
            return this;
        }

        long handle() {
            return handle;
        }

        Set<? extends WatchEvent.Kind events() {
            return events;
        }

        void setEvents(Set<? extends WatchEvent.Kind events) {
            this.events = events;
        }

        boolean watchSubtree() {
            return watchSubtree;
        }

        NativeBuffer buffer() {
            return buffer;
        }

        long countAddress() {
            return countAddress;
        }

        long overlappedAddress() {
            return overlappedAddress;
        }

        FileKey fileKey() {
            return fileKey;
        }

        int completionKey() {
            return completionKey;
        }

        // close directory and release buffer
        void releaseResources() {
            CloseHandle(handle);
            buffer.cleaner().clean();
        }

        // Invalidate key by closing directory and releasing buffer
        void invalidate() {
            releaseResources();
            handle = INVALID_HANDLE_VALUE;
            buffer = null;
            countAddress = 0;
            overlappedAddress = 0;
        }

        @Override
        public boolean isValid() {
            return handle != INVALID_HANDLE_VALUE;
        }

        @Override
        public void cancel() {
            if (isValid()) {
                // delegate to poller
                poller.cancel(this);
            }
        }
    }

    // file key to unique identify (open) directory
    private static class FileKey {
        private final int volSerialNumber;
        private final int fileIndexHigh;
        private final int fileIndexLow;

        FileKey(int volSerialNumber, int fileIndexHigh, int fileIndexLow) {
            this.volSerialNumber = volSerialNumber;
            this.fileIndexHigh = fileIndexHigh;
            this.fileIndexLow = fileIndexLow;
        }

        @Override
        public int hashCode() {
            return volSerialNumber ^ fileIndexHigh ^ fileIndexLow;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (!(obj instanceof FileKey))
                return false;
            FileKey other = (FileKey)obj;
            if (this.volSerialNumber != other.volSerialNumber) return false;
            if (this.fileIndexHigh != other.fileIndexHigh) return false;
            return this.fileIndexLow == other.fileIndexLow;
        }
    }

    // all change events
    private static final int ALL_FILE_NOTIFY_EVENTS =
        FILE_NOTIFY_CHANGE_FILE_NAME |
        FILE_NOTIFY_CHANGE_DIR_NAME |
        FILE_NOTIFY_CHANGE_ATTRIBUTES  |
        FILE_NOTIFY_CHANGE_SIZE |
        FILE_NOTIFY_CHANGE_LAST_WRITE |
        FILE_NOTIFY_CHANGE_CREATION |
        FILE_NOTIFY_CHANGE_SECURITY;

    /**
     * Background thread to service I/O completion port.
     */
    private class Poller extends AbstractPoller {
        /*
         * typedef struct _OVERLAPPED {
         *     DWORD  Internal;
         *     DWORD  InternalHigh;
         *     DWORD  Offset;
         *     DWORD  OffsetHigh;
         *     HANDLE hEvent;
         * } OVERLAPPED;
         */
        private static final short SIZEOF_DWORD         = 4;
        private static final short SIZEOF_OVERLAPPED    = 32; // 20 on 32-bit

        /*
         * typedef struct _FILE_NOTIFY_INFORMATION {
         *     DWORD NextEntryOffset;
         *     DWORD Action;
         *     DWORD FileNameLength;
         *     WCHAR FileName[1];
         * } FileNameLength;
         */
        private static final short OFFSETOF_NEXTENTRYOFFSET = 0;
        private static final short OFFSETOF_ACTION          = 4;
        private static final short OFFSETOF_FILENAMELENGTH  = 8;
        private static final short OFFSETOF_FILENAME        = 12;

        // size of per-directory buffer for events (FIXME - make this configurable)
        // Need to be less than 4*16384 = 65536. DWORD align.
        private static final int CHANGES_BUFFER_SIZE    = 16 * 1024;

        private final WindowsFileSystem fs;
        private final WindowsWatchService watcher;
        private final long port;

        // maps completion key to WatchKey
        private final Map<Integer,WindowsWatchKey> ck2key;

        // maps file key to WatchKey
        private final Map<FileKey,WindowsWatchKey> fk2key;

        // unique completion key for each directory
        // native completion key capacity is 64 bits on Win64.
        private int lastCompletionKey;

        Poller(WindowsFileSystem fs, WindowsWatchService watcher, long port) {
            this.fs = fs;
            this.watcher = watcher;
            this.port = port;
            this.ck2key = new HashMap<>();
            this.fk2key = new HashMap<>();
            this.lastCompletionKey = 0;
        }

        @Override
        void wakeup() throws IOException {
            try {
                PostQueuedCompletionStatus(port, WAKEUP_COMPLETION_KEY);
            } catch (WindowsException x) {
                throw new IOException(x.getMessage());
            }
        }

        /**
         * Register a directory for changes as follows:
         *
         * 1. Open directory
         * 2. Read its attributes (and check it really is a directory)
         * 3. Assign completion key and associated handle with completion port
         * 4. Call ReadDirectoryChangesW to start (async) read of changes
         * 5. Create or return existing key representing registration
         */
        @Override
        Object implRegister(Path obj,
                            Set<? extends WatchEvent.Kind events,
                            WatchEvent.Modifier... modifiers)
        {
            WindowsPath dir = (WindowsPath)obj;
            boolean watchSubtree = false;

            // FILE_TREE modifier allowed
            for (WatchEvent.Modifier modifier: modifiers) {
                if (modifier == ExtendedWatchEventModifier.FILE_TREE) {
                    watchSubtree = true;
                } else {
                    if (modifier == null)
                        return new NullPointerException();
                    if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
                        continue; // ignore
                    return new UnsupportedOperationException("Modifier not supported");
                }
            }

            // open directory
            long handle;
            try {
                handle = CreateFile(dir.getPathForWin32Calls(),
                                    FILE_LIST_DIRECTORY,
                                    (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
                                    OPEN_EXISTING,
                                    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED);
            } catch (WindowsException x) {
                return x.asIOException(dir);
            }

            boolean registered = false;
            try {
                // read attributes and check file is a directory
                WindowsFileAttributes attrs;
                try {
                    attrs = WindowsFileAttributes.readAttributes(handle);
                } catch (WindowsException x) {
                    return x.asIOException(dir);
                }
                if (!attrs.isDirectory()) {
                    return new NotDirectoryException(dir.getPathForExceptionMessage());
                }

                // check if this directory is already registered
                FileKey fk = new FileKey(attrs.volSerialNumber(),
                                         attrs.fileIndexHigh(),
                                         attrs.fileIndexLow());
                WindowsWatchKey existing = fk2key.get(fk);

                // if already registered and we're not changing the subtree
                // modifier then simply update the event and return the key.
                if (existing != null && watchSubtree == existing.watchSubtree()) {
                    existing.setEvents(events);
                    return existing;
                }

                // Can overflow the int type capacity.
                // Skip WAKEUP_COMPLETION_KEY value.
                int completionKey = ++lastCompletionKey;
                if (completionKey == WAKEUP_COMPLETION_KEY)
                    completionKey = ++lastCompletionKey;

                // associate handle with completion port
                try {
                    CreateIoCompletionPort(handle, port, completionKey);
                } catch (WindowsException x) {
                    return new IOException(x.getMessage());
                }

                // allocate memory for events, including space for other structures
                // needed to do overlapped I/O
                int size = CHANGES_BUFFER_SIZE + SIZEOF_DWORD + SIZEOF_OVERLAPPED;
                NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);

                long bufferAddress = buffer.address();
                long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED;
                long countAddress = overlappedAddress - SIZEOF_DWORD;

                // start async read of changes to directory
                try {
                    ReadDirectoryChangesW(handle,
                                          bufferAddress,
                                          CHANGES_BUFFER_SIZE,
                                          watchSubtree,
                                          ALL_FILE_NOTIFY_EVENTS,
                                          countAddress,
                                          overlappedAddress);
                } catch (WindowsException x) {
                    buffer.release();
                    return new IOException(x.getMessage());
                }

                WindowsWatchKey watchKey;
                if (existing == null) {
                    // not registered so create new watch key
                    watchKey = new WindowsWatchKey(dir, watcher, fk)
                        .init(handle, events, watchSubtree, buffer, countAddress,
                              overlappedAddress, completionKey);
                    // map file key to watch key
                    fk2key.put(fk, watchKey);
                } else {
                    // directory already registered so need to:
                    // 1. remove mapping from old completion key to existing watch key
                    // 2. release existing key's resources (handle/buffer)
                    // 3. re-initialize key with new handle/buffer
                    ck2key.remove(existing.completionKey());
                    existing.releaseResources();
                    watchKey = existing.init(handle, events, watchSubtree, buffer,
                        countAddress, overlappedAddress, completionKey);
                }
                // map completion map to watch key
                ck2key.put(completionKey, watchKey);

                registered = true;
                return watchKey;

            } finally {
                if (!registered) CloseHandle(handle);
            }
        }

        // cancel single key
        @Override
        void implCancelKey(WatchKey obj) {
            WindowsWatchKey key = (WindowsWatchKey)obj;
            if (key.isValid()) {
                fk2key.remove(key.fileKey());
                ck2key.remove(key.completionKey());
                key.invalidate();
            }
        }

        // close watch service
        @Override
        void implCloseAll() {
            // cancel all keys
            for (Map.Entry<Integer, WindowsWatchKey> entry: ck2key.entrySet()) {
                entry.getValue().invalidate();
            }
            fk2key.clear();
            ck2key.clear();

            // close I/O completion port
            CloseHandle(port);
        }

        // Translate file change action into watch event
        private WatchEvent.Kind<?> translateActionToEvent(int action)
        {
            switch (action) {
                case FILE_ACTION_MODIFIED :
                    return StandardWatchEventKinds.ENTRY_MODIFY;

                case FILE_ACTION_ADDED :
                case FILE_ACTION_RENAMED_NEW_NAME :
                    return StandardWatchEventKinds.ENTRY_CREATE;

                case FILE_ACTION_REMOVED :
                case FILE_ACTION_RENAMED_OLD_NAME :
                    return StandardWatchEventKinds.ENTRY_DELETE;

                default :
                    return null;  // action not recognized
            }
        }

        // process events (list of FILE_NOTIFY_INFORMATION structures)
        private void processEvents(WindowsWatchKey key, int size) {
            long address = key.buffer().address();

            int nextOffset;
            do {
                int action = unsafe.getInt(address + OFFSETOF_ACTION);

                // map action to event
                WatchEvent.Kind<?> kind = translateActionToEvent(action);
                if (key.events().contains(kind)) {
                    // copy the name
                    int nameLengthInBytes = unsafe.getInt(address + OFFSETOF_FILENAMELENGTH);
                    if ((nameLengthInBytes % 2) != 0) {
                        throw new AssertionError("FileNameLength.FileNameLength is not a multiple of 2");
                    }
                    char[] nameAsArray = new char[nameLengthInBytes/2];
                    unsafe.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray,
                        Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);

                    // create FileName and queue event
                    WindowsPath name = WindowsPath
                        .createFromNormalizedPath(fs, new String(nameAsArray));
                    key.signalEvent(kind, name);
                }

                // next event
                nextOffset = unsafe.getInt(address + OFFSETOF_NEXTENTRYOFFSET);
                address += (long)nextOffset;
            } while (nextOffset != 0);
        }

        /**
         * Poller main loop
         */
        @Override
        public void run() {
            for (;;) {
                CompletionStatus info;
                try {
                    info = GetQueuedCompletionStatus(port);
                } catch (WindowsException x) {
                    // this should not happen
                    x.printStackTrace();
                    return;
                }

                // wakeup
                if (info.completionKey() == WAKEUP_COMPLETION_KEY) {
                    boolean shutdown = processRequests();
                    if (shutdown) {
                        return;
                    }
                    continue;
                }

                // map completionKey to get WatchKey
                WindowsWatchKey key = ck2key.get((int)info.completionKey());
                if (key == null) {
                    // We get here when a registration is changed. In that case
                    // the directory is closed which causes an event with the
                    // old completion key.
                    continue;
                }

                boolean criticalError = false;
                int errorCode = info.error();
                int messageSize = info.bytesTransferred();
                if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
                    // buffer overflow
                    key.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
                } else if (errorCode != 0 && errorCode != ERROR_MORE_DATA) {
                    // ReadDirectoryChangesW failed
                    criticalError = true;
                } else {
                    // ERROR_MORE_DATA is a warning about incomplite
                    // data transfer over TCP/UDP stack. For the case
                    // [messageSize] is zero in the most of cases.

                    if (messageSize > 0) {
                        // process non-empty events.
                        processEvents(key, messageSize);
                    } else if (errorCode == 0) {
                        // insufficient buffer size
                        // not described, but can happen.
                        key.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
                    }

                    // start read for next batch of changes
                    try {
                        ReadDirectoryChangesW(key.handle(),
                                              key.buffer().address(),
                                              CHANGES_BUFFER_SIZE,
                                              key.watchSubtree(),
                                              ALL_FILE_NOTIFY_EVENTS,
                                              key.countAddress(),
                                              key.overlappedAddress());
                    } catch (WindowsException x) {
                        // no choice but to cancel key
                        criticalError = true;
                    }
                }
                if (criticalError) {
                    implCancelKey(key);
                    key.signal();
                }
            }
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java WindowsWatchService.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.