Logo Search packages:      
Sourcecode: libjna-java version File versions  Download package

FileMonitor.java

/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p/>
 * This library 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
 * Lesser General Public License for more details.  
 */
package com.sun.jna.examples;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.sun.jna.examples.win32.Kernel32;
import com.sun.jna.examples.win32.Kernel32.FILE_NOTIFY_INFORMATION;
import com.sun.jna.examples.win32.Kernel32.OVERLAPPED;
import com.sun.jna.examples.win32.W32API.HANDLE;
import com.sun.jna.examples.win32.W32API.HANDLEByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

/** Provides notification of file system changes.  Actual capabilities may
 * vary slightly by platform.
 * <p>
 * Watched files which are removed from the filesystem are no longer watched.
 * @author twall
 */

00039 public abstract class FileMonitor {

    public static final int FILE_CREATED = 0x1;
    public static final int FILE_DELETED = 0x2;
    public static final int FILE_MODIFIED = 0x4;
    public static final int FILE_ACCESSED = 0x8;
    public static final int FILE_NAME_CHANGED_OLD = 0x10;
    public static final int FILE_NAME_CHANGED_NEW = 0x20;
    public static final int FILE_RENAMED = FILE_NAME_CHANGED_OLD|FILE_NAME_CHANGED_NEW;
    public static final int FILE_SIZE_CHANGED = 0x40;
    public static final int FILE_ATTRIBUTES_CHANGED = 0x80;
    public static final int FILE_SECURITY_CHANGED = 0x100;
    public static final int FILE_ANY = 0x1FF;

    public interface FileListener {
        public void fileChanged(FileEvent e);
    }
    
    public class FileEvent extends EventObject {
        private final File file;
        private final int type;
        public FileEvent(File file, int type) {
            super(FileMonitor.this);
            this.file = file;
            this.type = type;
        }
        public File getFile() { return file; }
        public int getType() { return type; }
        public String toString() {
            return "FileEvent: " + file + ":" + type;
        }
    }
    
    private final Map watched = new HashMap();
    private List listeners = new ArrayList();
    
    protected abstract void watch(File file, int mask, boolean recursive) throws IOException ;
    protected abstract void unwatch(File file);
    protected abstract void dispose();

    public void addWatch(File dir) throws IOException {
        addWatch(dir, FILE_ANY);
    }
    
    public void addWatch(File dir, int mask) throws IOException {
        addWatch(dir, mask, dir.isDirectory());
    }
    
    public void addWatch(File dir, int mask, boolean recursive) throws IOException {
        watched.put(dir, new Integer(mask));
        watch(dir, mask, recursive);
    }

    public void removeWatch(File file) {
        if (watched.remove(file) != null) {
            unwatch(file);
        }
    }
    
    protected void notify(FileEvent e) {
        for (Iterator i=listeners.iterator();i.hasNext();) {
            ((FileListener)i.next()).fileChanged(e);
        }
    }
    
    public synchronized void addFileListener(FileListener x) {
        List list = new ArrayList(listeners);
        list.add(x);
        listeners = list;
    }
    
    public synchronized void removeFileListener(FileListener x) {
        List list = new ArrayList(listeners);
        list.remove(x);
        listeners = list;
    }
    
    protected void finalize() {
        for (Iterator i=watched.keySet().iterator();i.hasNext();) {
            removeWatch((File)i.next());
        }
        dispose();
    }
    
    /** Canonical lazy loading of a singleton. */
00124     private static class Holder {
        public static final FileMonitor INSTANCE;
        static {
            String os = System.getProperty("os.name");
            if (os.startsWith("Windows")) {
                INSTANCE = new W32FileMonitor();
            }
            else {
                throw new Error("FileMonitor not implemented for " + os);
            }
        }
    }
    
    public static FileMonitor getInstance() {
        return Holder.INSTANCE;
    }
    
    private static class W32FileMonitor extends FileMonitor {
        
        private static final int BUFFER_SIZE = 4096;
        private class FileInfo {
            public final File file;
            public final HANDLE handle;
            public final int notifyMask;
            public final boolean recursive;
            public final FILE_NOTIFY_INFORMATION info = new FILE_NOTIFY_INFORMATION(BUFFER_SIZE);
            public final IntByReference infoLength = new IntByReference();
            public final OVERLAPPED overlapped = new OVERLAPPED();
            public FileInfo(File f, HANDLE h, int mask, boolean recurse) {
                this.file = f;
                this.handle = h;
                this.notifyMask = mask;
                this.recursive = recurse;
            }
        }
        private Thread watcher;
        private HANDLE port;
        private final Map fileMap = new HashMap();
        private final Map handleMap = new HashMap();
        
        private void handleChanges(FileInfo finfo) throws IOException {
            Kernel32 klib = Kernel32.INSTANCE;
            FILE_NOTIFY_INFORMATION fni = finfo.info;
            // Need an explicit read, since data was filled in asynchronously
            fni.read();
            do {
                FileEvent event = null;
                File file = new File(finfo.file, fni.getFilename());
                switch(fni.Action) {
                case Kernel32.FILE_ACTION_MODIFIED:
                    event = new FileEvent(file, FILE_MODIFIED); break;
                case Kernel32.FILE_ACTION_ADDED:
                    event = new FileEvent(file, FILE_CREATED); break;
                case Kernel32.FILE_ACTION_REMOVED:
                    event = new FileEvent(file, FILE_DELETED); break;
                case Kernel32.FILE_ACTION_RENAMED_OLD_NAME:
                    event = new FileEvent(file, FILE_NAME_CHANGED_OLD); break;
                case Kernel32.FILE_ACTION_RENAMED_NEW_NAME:
                    event = new FileEvent(file, FILE_NAME_CHANGED_NEW); break;
                default:
                    // TODO: other actions...
                    System.err.println("Unrecognized file action '" + fni.Action + "'");
                }
                if (event != null)
                    notify(event);
                fni = fni.next();
            } while (fni != null);
            // Trigger the next read
            if (!finfo.file.exists()) {
                unwatch(finfo.file);
                return;
            }
            
            if (!klib.ReadDirectoryChangesW(finfo.handle, finfo.info,
                                            finfo.info.size(), finfo.recursive,
                                            finfo.notifyMask, finfo.infoLength, 
                                            finfo.overlapped, null)) {
                int err = klib.GetLastError();
                throw new IOException("ReadDirectoryChangesW failed on "
                                      + finfo.file + ": '" 
                                      + getSystemError(err)
                                      + "' (" + err + ")");
            }
        }
        private FileInfo waitForChange() {
            Kernel32 klib = Kernel32.INSTANCE;
            IntByReference rcount = new IntByReference();
            HANDLEByReference rkey = new HANDLEByReference();
            PointerByReference roverlap = new PointerByReference();
            klib.GetQueuedCompletionStatus(port, rcount, rkey, roverlap, Kernel32.INFINITE);
            
            synchronized (this) { 
                return (FileInfo)handleMap.get(rkey.getValue());
            }
        }
        private int convertMask(int mask) {
            int result = 0;
            if ((mask & FILE_CREATED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_CREATION;
            }
            if ((mask & FILE_DELETED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_NAME;
            }
            if ((mask & FILE_MODIFIED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_LAST_WRITE;
            }
            if ((mask & FILE_RENAMED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_NAME;
            }
            if ((mask & FILE_SIZE_CHANGED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_SIZE;
            }
            if ((mask & FILE_ACCESSED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_LAST_ACCESS;
            }
            if ((mask & FILE_ATTRIBUTES_CHANGED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_ATTRIBUTES;
            }
            if ((mask & FILE_SECURITY_CHANGED) != 0) {
                result |= Kernel32.FILE_NOTIFY_CHANGE_SECURITY;
            }
            return result;
        }

        private static int watcherThreadID;

        protected synchronized void watch(File file, int eventMask, boolean recursive) throws IOException {
            File dir = file;
            if (!dir.isDirectory()) {
                recursive = false;
                dir = file.getParentFile();
            }
            while (dir != null && !dir.exists()) {
                recursive = true;
                dir = dir.getParentFile();
            }
            if (dir == null) {
                throw new FileNotFoundException("No ancestor found for " + file);
            }
            Kernel32 klib = Kernel32.INSTANCE;
            int mask = Kernel32.FILE_SHARE_READ
                | Kernel32.FILE_SHARE_WRITE | Kernel32.FILE_SHARE_DELETE;
            int flags = Kernel32.FILE_FLAG_BACKUP_SEMANTICS
                | Kernel32.FILE_FLAG_OVERLAPPED;
            HANDLE handle = klib.CreateFile(file.getAbsolutePath(), 
                                            Kernel32.FILE_LIST_DIRECTORY,
                                            mask, null, Kernel32.OPEN_EXISTING,
                                            flags, null);
            if (Kernel32.INVALID_HANDLE_VALUE.equals(handle)) {
                throw new IOException("Unable to open " + file + " (" 
                                      + klib.GetLastError() + ")");
            }
            int notifyMask = convertMask(eventMask);
            FileInfo finfo = new FileInfo(file, handle, notifyMask, recursive);
            fileMap.put(file, finfo);
            handleMap.put(handle, finfo);
            // Existing port is returned
            port = klib.CreateIoCompletionPort(handle, port, handle.getPointer(), 0);
            if (Kernel32.INVALID_HANDLE_VALUE.equals(port)) {
                throw new IOException("Unable to create/use I/O Completion port "
                        + "for " + file + " ("
                        + klib.GetLastError() + ")");
            }
            // TODO: use FileIOCompletionRoutine callback method instead of a 
            // dedicated thread
            if (!klib.ReadDirectoryChangesW(handle, finfo.info, finfo.info.size(), 
                                            recursive, notifyMask, finfo.infoLength, 
                                            finfo.overlapped, null)) {
                int err = klib.GetLastError();
                throw new IOException("ReadDirectoryChangesW failed on "
                                      + finfo.file 
                                      + ": '" + getSystemError(err)
                                      + "' (" + err + ")");
            }
            if (watcher == null) {
                watcher = new Thread("W32 File Monitor-" + (watcherThreadID++)) {
                    public void run() {
                        FileInfo finfo;
                        while (true) {
                           finfo = waitForChange();
                           if (finfo == null) {
                              synchronized(W32FileMonitor.this) {
                                 if (fileMap.isEmpty()) {
                                    watcher = null;
                                    break;
                                 }
                              }
                              continue;
                            }
                           
                            try {
                                handleChanges(finfo);
                            }
                            catch(IOException e) {
                                // TODO: how is this best handled?
                                e.printStackTrace();
                            }
                        }
                    }
                };
                watcher.setDaemon(true);
                watcher.start();
            }
        }

        protected synchronized void unwatch(File file) {
            FileInfo finfo = (FileInfo)fileMap.remove(file);
            if (finfo != null) {
                handleMap.remove(finfo.handle);
                Kernel32 klib = Kernel32.INSTANCE;
                klib.CloseHandle(finfo.handle);
            }
        }
        protected synchronized void dispose() {
            // unwatch any remaining files in map, allows watcher thread to exit
            int i = 0;
            for (Object[] keys = fileMap.keySet().toArray(); !fileMap.isEmpty();) {
                unwatch((File)keys[i++]);
            }
            
            Kernel32 klib = Kernel32.INSTANCE;
            klib.PostQueuedCompletionStatus(port, 0, null, null);
            klib.CloseHandle(port);
            port = null;
            watcher = null;
        }
        private String getSystemError(int code) {
            Kernel32 lib = Kernel32.INSTANCE;
            PointerByReference pref = new PointerByReference();
            lib.FormatMessage(Kernel32.FORMAT_MESSAGE_ALLOCATE_BUFFER
                              |Kernel32.FORMAT_MESSAGE_FROM_SYSTEM
                              |Kernel32.FORMAT_MESSAGE_IGNORE_INSERTS, 
                              null, code, 
                              0, pref, 0, null);
            String s = pref.getValue().getString(0, !Boolean.getBoolean("w32.ascii"));
            lib.LocalFree(pref.getValue());
            return s;
        }
    }
    
    private static class KQueueFileMonitor extends FileMonitor {
        protected void watch(File file, int mask, boolean recursive) {
            
        }
        protected void unwatch(File file) {
        }
        protected void dispose() {
        }
    }
    
    private static class INotifyFileMonitor extends FileMonitor {
        protected void watch(File file, int mask, boolean recursive) {
            
        }
        protected void unwatch(File file) {
        }
        protected void dispose() {
        }
    }
}

Generated by  Doxygen 1.6.0   Back to index