Merge of split of masterfs into independent OS modules with most recent version of default branch masterfs-without-jna-206434
authorJaroslav Tulach <jtulach@netbeans.org>
Tue, 27 Dec 2011 19:31:42 +0100
branchmasterfs-without-jna-206434
changeset 216116912175856cfd
parent 216114 b406abfcef57
parent 216103 102b0d5d752f
child 216117 91eedbdb60f7
Merge of split of masterfs into independent OS modules with most recent version of default branch
masterfs.solaris/src/org/netbeans/modules/masterfs/watcher/solaris/FAMNotifier.java
masterfs/src/org/netbeans/modules/masterfs/watcher/FAMNotifier.java
masterfs/src/org/netbeans/modules/masterfs/watcher/LinuxNotifier.java
masterfs/src/org/netbeans/modules/masterfs/watcher/Notifier.java
masterfs/src/org/netbeans/modules/masterfs/watcher/OSXNotifier.java
masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java
masterfs/src/org/netbeans/modules/masterfs/watcher/WindowsNotifier.java
nbbuild/cluster.properties
     1.1 --- a/libs.jna/manifest.mf	Sat Dec 24 00:28:54 2011 +0000
     1.2 +++ b/libs.jna/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
     1.3 @@ -3,4 +3,4 @@
     1.4  OpenIDE-Module: org.netbeans.libs.jna/1
     1.5  OpenIDE-Module-Localizing-Bundle: org/netbeans/libs/jna/Bundle.properties
     1.6  AutoUpdate-Essential-Module: true
     1.7 -OpenIDE-Module-Specification-Version: 1.17
     1.8 +OpenIDE-Module-Specification-Version: 1.18
     2.1 --- a/libs.jna/nbproject/project.xml	Sat Dec 24 00:28:54 2011 +0000
     2.2 +++ b/libs.jna/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
     2.3 @@ -57,7 +57,10 @@
     2.4                  <friend>org.netbeans.modules.dlight.nativeexecution</friend>
     2.5                  <friend>org.netbeans.modules.extexecution.destroy</friend>
     2.6                  <friend>org.netbeans.modules.keyring.impl</friend>
     2.7 -                <friend>org.netbeans.modules.masterfs</friend>
     2.8 +                <friend>org.netbeans.modules.masterfs.windows</friend>
     2.9 +                <friend>org.netbeans.modules.masterfs.linux</friend>
    2.10 +                <friend>org.netbeans.modules.masterfs.solaris</friend>
    2.11 +                <friend>org.netbeans.modules.masterfs.macosx</friend>
    2.12                  <friend>org.netbeans.modules.python.qshell</friend>
    2.13                  <friend>org.netbeans.libs.svnClientAdapter.svnkit</friend>
    2.14                  <package>com.sun.jna</package>
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/masterfs.linux/build.xml	Tue Dec 27 19:31:42 2011 +0100
     3.3 @@ -0,0 +1,5 @@
     3.4 +<?xml version="1.0" encoding="UTF-8"?>
     3.5 +<project basedir="." default="netbeans" name="masterfs.linux">
     3.6 +    <description>Builds, tests, and runs the project org.netbeans.modules.masterfs.linux</description>
     3.7 +    <import file="../nbbuild/templates/projectized.xml"/>
     3.8 +</project>
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/masterfs.linux/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
     4.3 @@ -0,0 +1,7 @@
     4.4 +Manifest-Version: 1.0
     4.5 +OpenIDE-Module: org.netbeans.modules.masterfs.linux
     4.6 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/watcher/linux/Bundle.properties
     4.7 +OpenIDE-Module-Specification-Version: 1.0
     4.8 +OpenIDE-Module-Requires: org.openide.modules.os.Linux
     4.9 +OpenIDE-Module-Provides: org.netbeans.modules.masterfs.providers.Notifier
    4.10 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/masterfs.linux/nbproject/project.properties	Tue Dec 27 19:31:42 2011 +0100
     5.3 @@ -0,0 +1,3 @@
     5.4 +is.autoload=true
     5.5 +javac.source=1.6
     5.6 +javac.compilerargs=-Xlint -Xlint:-serial
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/masterfs.linux/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
     6.3 @@ -0,0 +1,35 @@
     6.4 +<?xml version="1.0" encoding="UTF-8"?>
     6.5 +<project xmlns="http://www.netbeans.org/ns/project/1">
     6.6 +    <type>org.netbeans.modules.apisupport.project</type>
     6.7 +    <configuration>
     6.8 +        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
     6.9 +            <code-name-base>org.netbeans.modules.masterfs.linux</code-name-base>
    6.10 +            <module-dependencies>
    6.11 +                <dependency>
    6.12 +                    <code-name-base>org.netbeans.libs.jna</code-name-base>
    6.13 +                    <build-prerequisite/>
    6.14 +                    <compile-dependency/>
    6.15 +                    <run-dependency>
    6.16 +                        <release-version>1</release-version>
    6.17 +                        <specification-version>1.18</specification-version>
    6.18 +                    </run-dependency>
    6.19 +                </dependency>
    6.20 +                <dependency>
    6.21 +                    <code-name-base>org.netbeans.modules.masterfs</code-name-base>
    6.22 +                    <build-prerequisite/>
    6.23 +                    <compile-dependency/>
    6.24 +                    <run-dependency>
    6.25 +                        <release-version>2</release-version>
    6.26 +                        <specification-version>2.36</specification-version>
    6.27 +                    </run-dependency>
    6.28 +                </dependency>
    6.29 +                <dependency>
    6.30 +                    <code-name-base>org.openide.util.lookup</code-name-base>
    6.31 +                    <build-prerequisite/>
    6.32 +                    <compile-dependency/>
    6.33 +                </dependency>
    6.34 +            </module-dependencies>
    6.35 +            <public-packages/>
    6.36 +        </data>
    6.37 +    </configuration>
    6.38 +</project>
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/masterfs.linux/src/org/netbeans/modules/masterfs/watcher/linux/Bundle.properties	Tue Dec 27 19:31:42 2011 +0100
     7.3 @@ -0,0 +1,1 @@
     7.4 +OpenIDE-Module-Name=Master Filesystem @ Linux
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/masterfs.linux/src/org/netbeans/modules/masterfs/watcher/linux/LinuxNotifier.java	Tue Dec 27 19:31:42 2011 +0100
     8.3 @@ -0,0 +1,215 @@
     8.4 +/*
     8.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 + *
     8.7 + * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
     8.8 + *
     8.9 + * The contents of this file are subject to the terms of either the GNU
    8.10 + * General Public License Version 2 only ("GPL") or the Common
    8.11 + * Development and Distribution License("CDDL") (collectively, the
    8.12 + * "License"). You may not use this file except in compliance with the
    8.13 + * License. You can obtain a copy of the License at
    8.14 + * http://www.netbeans.org/cddl-gplv2.html
    8.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    8.16 + * specific language governing permissions and limitations under the
    8.17 + * License.  When distributing the software, include this License Header
    8.18 + * Notice in each file and include the License file at
    8.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    8.20 + * particular file as subject to the "Classpath" exception as provided
    8.21 + * by Sun in the GPL Version 2 section of the License file that
    8.22 + * accompanied this code. If applicable, add the following below the
    8.23 + * License Header, with the fields enclosed by brackets [] replaced by
    8.24 + * your own identifying information:
    8.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    8.26 + *
    8.27 + * If you wish your version of this file to be governed by only the CDDL
    8.28 + * or only the GPL Version 2, indicate your decision by adding
    8.29 + * "[Contributor] elects to include this software in this distribution
    8.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    8.31 + * single choice of license, a recipient has the option to distribute
    8.32 + * your version of this file under either the CDDL, the GPL Version 2 or
    8.33 + * to extend the choice of license to its licensees as provided above.
    8.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    8.35 + * Version 2 license, then the option applies only if the new code is
    8.36 + * made subject to such option by the copyright holder.
    8.37 + *
    8.38 + * Contributor(s):
    8.39 + *
    8.40 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
    8.41 + */
    8.42 +
    8.43 +package org.netbeans.modules.masterfs.watcher.linux;
    8.44 +
    8.45 +import org.netbeans.modules.masterfs.providers.Notifier;
    8.46 +import com.sun.jna.Library;
    8.47 +import com.sun.jna.Native;
    8.48 +import com.sun.jna.NativeLibrary;
    8.49 +import java.io.IOException;
    8.50 +import java.nio.ByteBuffer;
    8.51 +import java.nio.ByteOrder;
    8.52 +import java.util.HashMap;
    8.53 +import java.util.Map;
    8.54 +import java.util.logging.Level;
    8.55 +import java.util.logging.Logger;
    8.56 +import org.openide.util.lookup.ServiceProvider;
    8.57 +
    8.58 +/**
    8.59 + * A {@link Notifier} implementation based on Linux inotify mechanism.
    8.60 + *
    8.61 + * @author nenik
    8.62 + */
    8.63 +@ServiceProvider(service=Notifier.class, position=500)
    8.64 +public final class LinuxNotifier extends Notifier<LinuxNotifier.LKey> {
    8.65 +    private static final Logger LOG = Logger.getLogger(LinuxNotifier.class.getName());
    8.66 +
    8.67 +    private static interface InotifyImpl extends Library {
    8.68 +	public int inotify_init();
    8.69 +	public int inotify_init1(int flags);
    8.70 +	public int close(int fd);
    8.71 +    public int read(int fd, ByteBuffer buff, int count);
    8.72 +	public int inotify_add_watch(int fd, String pathname, int mask);
    8.73 +	public int inotify_rm_watch(int fd, int wd);
    8.74 +
    8.75 +        public static final int O_CLOEXEC = 02000000; //0x80000
    8.76 +
    8.77 +        // Masks
    8.78 +        public static final int IN_ACCESS = 0x0001;      /* File was accessed */
    8.79 +        public static final int IN_MODIFY = 0x0002;      /* File was modified */
    8.80 +        public static final int IN_ATTRIB = 0x0004;      /* Metadata changed */
    8.81 +        public static final int IN_CLOSE_WRITE =   0x0008;      /* Writtable file was closed */
    8.82 +        public static final int IN_CLOSE_NOWRITE = 0x0010;      /* Unwrittable file closed */
    8.83 +        public static final int IN_OPEN =          0x0020;      /* File was opened */
    8.84 +        public static final int IN_MOVED_FROM =   0x0040;      /* File was moved from X */
    8.85 +        public static final int IN_MOVED_TO =     0x0080;      /* File was moved to Y */
    8.86 +        public static final int IN_CREATE =        0x0100;      /* Subfile was created */
    8.87 +        public static final int IN_DELETE =        0x0200;      /* Subfile was deleted */
    8.88 +        public static final int IN_DELETE_SELF =    0x0400;      /* Self was deleted */
    8.89 +        public static final int IN_MOVE_SELF =     0x0800;      /* Self was moved */
    8.90 +
    8.91 +        // additional event masks
    8.92 +        public static final int IN_UNMOUNT =      0x2000;      /* Backing fs was unmounted */
    8.93 +        public static final int IN_Q_OVERFLOW =    0x4000;      /* Event queued overflowed */
    8.94 +        public static final int IN_IGNORED =       0x8000;      /* File was ignored */
    8.95 +
    8.96 +    }
    8.97 +
    8.98 +    final InotifyImpl IMPL;
    8.99 +    int fd;
   8.100 +    private ByteBuffer buff = ByteBuffer.allocateDirect(4096);
   8.101 +
   8.102 +    // An array would serve nearly as well
   8.103 +    private Map<Integer, LKey> map = new HashMap<Integer, LKey>();
   8.104 +
   8.105 +    public LinuxNotifier() {
   8.106 +        IMPL = (InotifyImpl) Native.loadLibrary("c", InotifyImpl.class);
   8.107 +    }
   8.108 +
   8.109 +    private String getString(int maxLen) {
   8.110 +        if (maxLen < 1) return null; // no name field
   8.111 +        int stop = maxLen - 1;
   8.112 +        byte[] temp = new byte[maxLen];
   8.113 +        buff.get(temp);
   8.114 +        while (temp[stop] == 0) stop--;
   8.115 +        return new String(temp, 0, stop+1);
   8.116 +    }
   8.117 +
   8.118 +    @Override public String nextEvent() throws IOException {
   8.119 +        /* inotify event structure layout:
   8.120 +         *   int      wd;    // Watch descriptor
   8.121 +         *   uint32_t mask;  // Mask of events
   8.122 +         *   uint32_t cookie;// Unique cookie associating related events (for rename(2))
   8.123 +         *   uint32_t len;   // Size of name field
   8.124 +         *   char     name[];// Optional null-terminated name
   8.125 +         */
   8.126 +        while (buff.remaining() < 16 || buff.remaining() < 16 + buff.getInt(buff.position() + 12)) {
   8.127 +            buff.compact();
   8.128 +            int len = IMPL.read(fd, buff, buff.remaining());
   8.129 +
   8.130 +            if (len <= 0) {
   8.131 +                // lazily get a thread local errno
   8.132 +                int errno = NativeLibrary.getInstance("c").getFunction("errno").getInt(0);
   8.133 +                if (errno == 4) { // EINTR
   8.134 +                    buff.flip();
   8.135 +                    continue; // restart the I/O 
   8.136 +                } else {
   8.137 +                    throw new IOException("error reading from inotify: " + errno);
   8.138 +                }
   8.139 +            }
   8.140 +            buff.position(buff.position() + len);
   8.141 +            buff.flip();
   8.142 +        }
   8.143 +
   8.144 +        // now we have enough data in the buffer
   8.145 +        int wd = buff.getInt();
   8.146 +        int mask = buff.getInt();
   8.147 +        int cookie = buff.getInt();
   8.148 +        int len = buff.getInt();
   8.149 +        String name = getString(len); // ignore
   8.150 +
   8.151 +        LKey key = map.get(wd);
   8.152 +        if (key == null) { /* wd == -1 -> Queue overflow */
   8.153 +            return null;
   8.154 +        }
   8.155 +        
   8.156 +        return key.path;
   8.157 +    }
   8.158 +
   8.159 +
   8.160 +    static class LKey {
   8.161 +        int id;
   8.162 +        String path;
   8.163 +
   8.164 +        public LKey(int id, String path) {
   8.165 +            this.id = id;
   8.166 +            this.path = path;
   8.167 +        }
   8.168 +
   8.169 +        @Override
   8.170 +        public String toString() {
   8.171 +            return "LKey[" + id + " - '" + path + "']";
   8.172 +        }
   8.173 +    }
   8.174 +
   8.175 +    @Override
   8.176 +    protected void start() throws IOException {
   8.177 +        buff.position(buff.capacity()); // make the buffer empty
   8.178 +        buff.order(ByteOrder.nativeOrder());
   8.179 +        fd = IMPL.inotify_init1(InotifyImpl.O_CLOEXEC);
   8.180 +        if (fd < 0) {
   8.181 +            LOG.log(
   8.182 +                    Level.INFO, "Linux kernel {0} returned {1} from inotify_init1",
   8.183 +                    new Object[]{System.getProperty("os.version"), fd});
   8.184 +            fd = IMPL.inotify_init();
   8.185 +            LOG.log(Level.INFO, "Trying inotify_init: {0}", fd);
   8.186 +        }
   8.187 +        if (fd < 0) {
   8.188 +            throw new IOException("inotify_init failed: " + fd);
   8.189 +        }
   8.190 +    }
   8.191 +
   8.192 +    @Override public LKey addWatch(String path) throws IOException {
   8.193 +        // what if the file doesn't exist?
   8.194 +        int id = IMPL.inotify_add_watch(fd, path,
   8.195 +                    InotifyImpl.IN_CREATE | InotifyImpl.IN_MOVED_TO |
   8.196 +                    InotifyImpl.IN_DELETE | InotifyImpl.IN_MOVED_FROM |
   8.197 +                    InotifyImpl.IN_MODIFY | InotifyImpl.IN_ATTRIB);
   8.198 +        //XXX handle error return value (-1)
   8.199 +        LOG.log(Level.FINEST, "addWatch{0} res: {1}", new Object[]{path, id});
   8.200 +        if (id <= 0) {
   8.201 +            // 28 == EINOSPC
   8.202 +            int errno = NativeLibrary.getInstance("c").getFunction("errno").getInt(0); // NOI18N
   8.203 +            throw new IOException("addWatch on " + path + " errno: " + errno); // NOI18N
   8.204 +        }
   8.205 +
   8.206 +        LKey newKey = map.get(id);
   8.207 +        if (newKey == null) {
   8.208 +            newKey = new LKey(id, path);
   8.209 +            map.put(id, newKey);
   8.210 +        }
   8.211 +        return newKey;
   8.212 +    }
   8.213 +
   8.214 +    @Override public void removeWatch(LKey lkey) {
   8.215 +        map.remove(lkey.id);
   8.216 +        IMPL.inotify_rm_watch(fd, lkey.id);
   8.217 +    }
   8.218 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/masterfs.macosx/build.xml	Tue Dec 27 19:31:42 2011 +0100
     9.3 @@ -0,0 +1,5 @@
     9.4 +<?xml version="1.0" encoding="UTF-8"?>
     9.5 +<project basedir="." default="netbeans" name="masterfs.macosx">
     9.6 +    <description>Builds, tests, and runs the project org.netbeans.modules.masterfs.macosx</description>
     9.7 +    <import file="../nbbuild/templates/projectized.xml"/>
     9.8 +</project>
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/masterfs.macosx/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
    10.3 @@ -0,0 +1,7 @@
    10.4 +Manifest-Version: 1.0
    10.5 +OpenIDE-Module: org.netbeans.modules.masterfs.macosx
    10.6 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/watcher/macosx/Bundle.properties
    10.7 +OpenIDE-Module-Specification-Version: 1.0
    10.8 +OpenIDE-Module-Requires: org.openide.modules.os.MacOSX
    10.9 +OpenIDE-Module-Provides: org.netbeans.modules.masterfs.providers.Notifier
   10.10 +
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/masterfs.macosx/nbproject/project.properties	Tue Dec 27 19:31:42 2011 +0100
    11.3 @@ -0,0 +1,3 @@
    11.4 +is.autoload=true
    11.5 +javac.source=1.6
    11.6 +javac.compilerargs=-Xlint -Xlint:-serial
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/masterfs.macosx/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
    12.3 @@ -0,0 +1,35 @@
    12.4 +<?xml version="1.0" encoding="UTF-8"?>
    12.5 +<project xmlns="http://www.netbeans.org/ns/project/1">
    12.6 +    <type>org.netbeans.modules.apisupport.project</type>
    12.7 +    <configuration>
    12.8 +        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
    12.9 +            <code-name-base>org.netbeans.modules.masterfs.macosx</code-name-base>
   12.10 +            <module-dependencies>
   12.11 +                <dependency>
   12.12 +                    <code-name-base>org.netbeans.libs.jna</code-name-base>
   12.13 +                    <build-prerequisite/>
   12.14 +                    <compile-dependency/>
   12.15 +                    <run-dependency>
   12.16 +                        <release-version>1</release-version>
   12.17 +                        <specification-version>1.18</specification-version>
   12.18 +                    </run-dependency>
   12.19 +                </dependency>
   12.20 +                <dependency>
   12.21 +                    <code-name-base>org.netbeans.modules.masterfs</code-name-base>
   12.22 +                    <build-prerequisite/>
   12.23 +                    <compile-dependency/>
   12.24 +                    <run-dependency>
   12.25 +                        <release-version>2</release-version>
   12.26 +                        <specification-version>2.36</specification-version>
   12.27 +                    </run-dependency>
   12.28 +                </dependency>
   12.29 +                <dependency>
   12.30 +                    <code-name-base>org.openide.util.lookup</code-name-base>
   12.31 +                    <build-prerequisite/>
   12.32 +                    <compile-dependency/>
   12.33 +                </dependency>
   12.34 +            </module-dependencies>
   12.35 +            <public-packages/>
   12.36 +        </data>
   12.37 +    </configuration>
   12.38 +</project>
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/masterfs.macosx/src/org/netbeans/modules/masterfs/watcher/macosx/Bundle.properties	Tue Dec 27 19:31:42 2011 +0100
    13.3 @@ -0,0 +1,1 @@
    13.4 +OpenIDE-Module-Name=Master Filesystem @ Mac OS X
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/masterfs.macosx/src/org/netbeans/modules/masterfs/watcher/macosx/OSXNotifier.java	Tue Dec 27 19:31:42 2011 +0100
    14.3 @@ -0,0 +1,293 @@
    14.4 +/*
    14.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    14.6 + *
    14.7 + * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    14.8 + *
    14.9 + * The contents of this file are subject to the terms of either the GNU
   14.10 + * General Public License Version 2 only ("GPL") or the Common
   14.11 + * Development and Distribution License("CDDL") (collectively, the
   14.12 + * "License"). You may not use this file except in compliance with the
   14.13 + * License. You can obtain a copy of the License at
   14.14 + * http://www.netbeans.org/cddl-gplv2.html
   14.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   14.16 + * specific language governing permissions and limitations under the
   14.17 + * License.  When distributing the software, include this License Header
   14.18 + * Notice in each file and include the License file at
   14.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   14.20 + * particular file as subject to the "Classpath" exception as provided
   14.21 + * by Sun in the GPL Version 2 section of the License file that
   14.22 + * accompanied this code. If applicable, add the following below the
   14.23 + * License Header, with the fields enclosed by brackets [] replaced by
   14.24 + * your own identifying information:
   14.25 + * "Portions Copyrighted [year] [name of copyright owner]"
   14.26 + *
   14.27 + * If you wish your version of this file to be governed by only the CDDL
   14.28 + * or only the GPL Version 2, indicate your decision by adding
   14.29 + * "[Contributor] elects to include this software in this distribution
   14.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   14.31 + * single choice of license, a recipient has the option to distribute
   14.32 + * your version of this file under either the CDDL, the GPL Version 2 or
   14.33 + * to extend the choice of license to its licensees as provided above.
   14.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   14.35 + * Version 2 license, then the option applies only if the new code is
   14.36 + * made subject to such option by the copyright holder.
   14.37 + *
   14.38 + * Contributor(s):
   14.39 + *
   14.40 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
   14.41 + */
   14.42 +
   14.43 +package org.netbeans.modules.masterfs.watcher.macosx;
   14.44 +
   14.45 +import org.netbeans.modules.masterfs.providers.Notifier;
   14.46 +import com.sun.jna.Callback;
   14.47 +import com.sun.jna.Library;
   14.48 +import com.sun.jna.Native;
   14.49 +import com.sun.jna.NativeLong;
   14.50 +import com.sun.jna.Pointer;
   14.51 +import java.io.IOException;
   14.52 +import java.io.InterruptedIOException;
   14.53 +import java.util.concurrent.BlockingQueue;
   14.54 +import java.util.concurrent.Exchanger;
   14.55 +import java.util.concurrent.ExecutorService;
   14.56 +import java.util.concurrent.Executors;
   14.57 +import java.util.concurrent.LinkedBlockingQueue;
   14.58 +import java.util.concurrent.ThreadFactory;
   14.59 +import java.util.logging.Level;
   14.60 +import java.util.logging.Logger;
   14.61 +import org.openide.util.lookup.ServiceProvider;
   14.62 +
   14.63 +/**
   14.64 + *
   14.65 + * @author Tomas Zezula
   14.66 + */
   14.67 +@ServiceProvider(service=Notifier.class, position=300)
   14.68 +public final class OSXNotifier extends Notifier<Void> {
   14.69 +    private static final Level DEBUG_LOG_LEVEL = Level.FINE;
   14.70 +    private static final Level PERF_LOG_LEVEL = Level.FINE;
   14.71 +    private static final long kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFFL;
   14.72 +    private static final int kFSEventStreamCreateFlagNoDefer = 0x00000002;
   14.73 +    private static final int kFSEventStreamEventFlagMustScanSubDirs = 0x00000001;
   14.74 +    private static final int kFSEventStreamEventFlagMount = 0x00000040;
   14.75 +    private static final int kFSEventStreamEventFlagUnmount = 0x00000080;
   14.76 +    private static final double LATENCY = 1.0f;
   14.77 +    private static final int ENC_MAC_ROMAN = 0;
   14.78 +    private static final String DEFAULT_RUN_LOOP_MODE = "kCFRunLoopDefaultMode";    //NOI18N
   14.79 +    private static final Logger LOG = Logger.getLogger(OSXNotifier.class.getName());
   14.80 +    private final CoreFoundation cf;
   14.81 +    private final CoreServices cs;
   14.82 +    private final EventCallback callback;
   14.83 +    private final BlockingQueue<String> events;
   14.84 +    //@GuardedBy("this")
   14.85 +    private ExecutorService worker;
   14.86 +    //@GuardedBy("this")
   14.87 +    private Pointer[] rtData;
   14.88 +    
   14.89 +    private static final String ALL_CHANGE = "ALL-CHANGE";  //xxx - shouldn't be global in Notifier rather than using null?
   14.90 +
   14.91 +    public OSXNotifier() {
   14.92 +        cf = (CoreFoundation) Native.loadLibrary("CoreFoundation",CoreFoundation.class);    //NOI18N
   14.93 +        cs = (CoreServices) Native.loadLibrary("CoreServices",CoreServices.class);          //NOI18N
   14.94 +        callback = new EventCallbackImpl();
   14.95 +        events = new LinkedBlockingQueue<String>();
   14.96 +    }
   14.97 +
   14.98 +
   14.99 +    public @Override Void addWatch(String path) throws IOException {
  14.100 +        return null;
  14.101 +    }
  14.102 +
  14.103 +    public @Override void removeWatch(Void key) throws IOException {
  14.104 +        // ignore
  14.105 +    }
  14.106 +
  14.107 +    public @Override String nextEvent() throws IOException, InterruptedException {
  14.108 +        final String event = events.take();
  14.109 +        return event == ALL_CHANGE ? null : event;
  14.110 +    }
  14.111 +
  14.112 +    public synchronized void start() throws IOException {
  14.113 +        if (worker != null) {
  14.114 +            throw new IllegalStateException("FileSystemWatcher already started.");  //NOI18N
  14.115 +        }
  14.116 +        worker = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
  14.117 +        final Exchanger<Object> exchanger = new Exchanger<Object>();
  14.118 +        worker.execute(new Runnable() {
  14.119 +            public @Override void run () {
  14.120 +                try {
  14.121 +                    Pointer[] _rtData = null;
  14.122 +                    try {
  14.123 +                        _rtData = createFSEventStream();
  14.124 +                    } catch (Throwable ex) {
  14.125 +                        exchanger.exchange(ex);
  14.126 +                    } finally {
  14.127 +                        if (_rtData != null) {
  14.128 +                            exchanger.exchange(_rtData);
  14.129 +                            cf.CFRunLoopRun();
  14.130 +                        }
  14.131 +                    }
  14.132 +                } catch (InterruptedException ie) {
  14.133 +                    LOG.log(Level.WARNING, "Watcher interruped during start", ie);  //NOI18N
  14.134 +                }
  14.135 +            }
  14.136 +        });
  14.137 +        final Object _data;
  14.138 +        try {
  14.139 +            _data = exchanger.exchange(null);
  14.140 +        } catch (InterruptedException ex) {
  14.141 +            throw (InterruptedIOException)new InterruptedIOException().initCause(ex);
  14.142 +        }
  14.143 +        assert _data != null;
  14.144 +        if (_data instanceof Throwable) {
  14.145 +            worker.shutdown();
  14.146 +            worker = null;
  14.147 +            throw new IOException((Throwable)_data);
  14.148 +        } else {
  14.149 +            rtData = (Pointer[]) _data;
  14.150 +        }
  14.151 +    }
  14.152 +
  14.153 +    @Override
  14.154 +    public synchronized void stop() throws IOException {
  14.155 +        if (worker == null) {
  14.156 +            throw new IllegalStateException("FileSystemWatcher is not started.");  //NOI18N
  14.157 +        }
  14.158 +        assert rtData != null;
  14.159 +        assert rtData.length == 2;
  14.160 +        assert rtData[0] != null;
  14.161 +        assert rtData[1] != null;
  14.162 +        cs.FSEventStreamStop(rtData[0]);
  14.163 +        cs.FSEventStreamInvalidate(rtData[0]);
  14.164 +        cs.FSEventStreamRelease(rtData[0]);
  14.165 +        cf.CFRunLoopStop(rtData[1]);
  14.166 +        worker.shutdown();
  14.167 +        worker = null;
  14.168 +        rtData = null;
  14.169 +    }
  14.170 +
  14.171 +    private Pointer[] createFSEventStream() throws IOException {
  14.172 +        final Pointer root = cf.CFStringCreateWithCString(Pointer.NULL,"/",ENC_MAC_ROMAN);  //NOI18N
  14.173 +        if (root == Pointer.NULL) {
  14.174 +            throw new IOException("Path creation failed.");     //NOI18N
  14.175 +        }
  14.176 +        final Pointer arr = cf.CFArrayCreateMutable(Pointer.NULL, new NativeLong(1), Pointer.NULL);
  14.177 +        if (arr == Pointer.NULL) {
  14.178 +            throw new IOException("Path list creation failed.");    //NOI18N
  14.179 +        }
  14.180 +        cf.CFArrayAppendValue(arr, root);
  14.181 +
  14.182 +        final Pointer eventStream = cs.FSEventStreamCreate(Pointer.NULL, callback, Pointer.NULL, arr, kFSEventStreamEventIdSinceNow, LATENCY, kFSEventStreamCreateFlagNoDefer);
  14.183 +        if (eventStream == Pointer.NULL) {
  14.184 +            throw new IOException("Creation of FSEventStream failed."); //NOI18N
  14.185 +        }
  14.186 +        final Pointer loop = cf.CFRunLoopGetCurrent();
  14.187 +        if (eventStream == Pointer.NULL) {
  14.188 +            throw new IOException("Cannot find run loop for caller.");  //NOI18N
  14.189 +        }
  14.190 +        final Pointer kCFRunLoopDefaultMode = findDefaultMode(loop);
  14.191 +        if (kCFRunLoopDefaultMode == null) {
  14.192 +            throw new IOException("Caller has no defaul run loop mode.");   //NOI18N
  14.193 +        }
  14.194 +        cs.FSEventStreamScheduleWithRunLoop(eventStream, loop, kCFRunLoopDefaultMode);
  14.195 +        if (LOG.isLoggable(DEBUG_LOG_LEVEL)) {
  14.196 +            LOG.log(DEBUG_LOG_LEVEL, getStreamDescription(eventStream));
  14.197 +        }
  14.198 +        cs.FSEventStreamStart(eventStream);
  14.199 +        return new Pointer[] {eventStream, loop};
  14.200 +    }
  14.201 +
  14.202 +    private Pointer findDefaultMode(final Pointer runLoop) {
  14.203 +        final Pointer modes = cf.CFRunLoopCopyAllModes(runLoop);
  14.204 +        if (modes != Pointer.NULL) {
  14.205 +            final int modesCount = cf.CFArrayGetCount(modes).intValue();
  14.206 +            for (int i=0; i< modesCount; i++) {
  14.207 +                final Pointer mode = cf.CFArrayGetValueAtIndex(modes, new NativeLong(i));
  14.208 +                if (mode != Pointer.NULL && DEFAULT_RUN_LOOP_MODE.equals(cf.CFStringGetCStringPtr(mode, ENC_MAC_ROMAN))) {
  14.209 +                    return mode;
  14.210 +                }
  14.211 +            }
  14.212 +        }
  14.213 +        return null;
  14.214 +    }
  14.215 +
  14.216 +    private String getStreamDescription(final Pointer eventStream) {
  14.217 +        final Pointer desc = cs.FSEventStreamCopyDescription(eventStream);
  14.218 +        return desc == Pointer.NULL ? "" : cf.CFStringGetCStringPtr(desc, ENC_MAC_ROMAN);   //NOI18N
  14.219 +    }
  14.220 +
  14.221 +    public static interface EventCallback extends Callback {
  14.222 +        void invoke(Pointer streamRef,
  14.223 +                    Pointer clientCallBackInfo,
  14.224 +                    NativeLong numEvents,
  14.225 +                    Pointer eventPaths,
  14.226 +                    Pointer eventFlags,
  14.227 +                    Pointer eventIds);
  14.228 +    }
  14.229 +
  14.230 +    public static interface CoreFoundation extends Library {
  14.231 +        Pointer CFRunLoopGetCurrent();
  14.232 +        void CFRunLoopRun();
  14.233 +        void CFRunLoopStop(Pointer loop);
  14.234 +        Pointer CFRunLoopCopyAllModes(Pointer loop);
  14.235 +
  14.236 +        Pointer CFArrayCreateMutable(Pointer allocator, NativeLong size, Pointer callback);
  14.237 +        void CFArrayAppendValue(Pointer theArray, Pointer value);
  14.238 +        Pointer CFArrayGetValueAtIndex(Pointer theArray, NativeLong index);
  14.239 +        NativeLong CFArrayGetCount(Pointer theArray);
  14.240 +
  14.241 +        Pointer CFStringCreateWithCString(Pointer allocator, String string, int encoding);
  14.242 +        String CFStringGetCStringPtr(Pointer theString, int encoding);
  14.243 +    }
  14.244 +
  14.245 +    public static interface CoreServices extends Library {
  14.246 +        Pointer FSEventStreamCreate(Pointer allocator, EventCallback callback, Pointer ctx, Pointer pathsToWatch, long sinceWhen, double latency, int flags);
  14.247 +        Pointer FSEventStreamCopyDescription(Pointer stream);
  14.248 +        void FSEventStreamScheduleWithRunLoop(Pointer stream, Pointer loop, Pointer mode);
  14.249 +        void FSEventStreamUnscheduleFromRunLoop(Pointer stream, Pointer loop, Pointer mode);
  14.250 +        void FSEventStreamStart(Pointer stream);
  14.251 +        void FSEventStreamStop(Pointer stream);
  14.252 +        void FSEventStreamInvalidate(Pointer stream);
  14.253 +        void FSEventStreamRelease(Pointer stream);
  14.254 +    }
  14.255 +
  14.256 +    private static class DaemonThreadFactory implements ThreadFactory {
  14.257 +        @Override
  14.258 +        public Thread newThread(Runnable r) {
  14.259 +            final Thread t = new Thread(r);
  14.260 +            t.setDaemon(true);
  14.261 +            return t;
  14.262 +        }
  14.263 +
  14.264 +    }
  14.265 +
  14.266 +    private class EventCallbackImpl implements EventCallback {
  14.267 +        @Override
  14.268 +        public void invoke(Pointer streamRef, Pointer clientCallBackInfo, NativeLong numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds) {
  14.269 +            final long st = System.currentTimeMillis();
  14.270 +            final int length = numEvents.intValue();
  14.271 +            final Pointer[] pointers = eventPaths.getPointerArray(0, length);
  14.272 +            int flags[];
  14.273 +            if (eventFlags == null) {
  14.274 +                flags = new int[length];
  14.275 +                LOG.log(DEBUG_LOG_LEVEL, "FSEventStreamCallback eventFlags == null, expected int[] of size {0}", length); //NOI18N
  14.276 +            } else {
  14.277 +                flags = eventFlags.getIntArray(0, length);
  14.278 +            }
  14.279 +            for (int i=0; i<length; i++) {
  14.280 +                final Pointer p = pointers[i];
  14.281 +                int flag = flags[i];
  14.282 +                final String path = p.getString(0);
  14.283 +
  14.284 +                if ((flag & kFSEventStreamEventFlagMustScanSubDirs) ==  kFSEventStreamEventFlagMustScanSubDirs ||
  14.285 +                    (flag & kFSEventStreamEventFlagMount) == kFSEventStreamEventFlagMount ||
  14.286 +                    (flag & kFSEventStreamEventFlagUnmount) == kFSEventStreamEventFlagUnmount) {
  14.287 +                    events.add(ALL_CHANGE);
  14.288 +                } else {
  14.289 +                    events.add(path);
  14.290 +                }
  14.291 +                LOG.log(DEBUG_LOG_LEVEL, "Event on {0}", new Object[]{path});
  14.292 +            }
  14.293 +            LOG.log(PERF_LOG_LEVEL, "Callback time: {0}", (System.currentTimeMillis() - st));
  14.294 +        }
  14.295 +    }
  14.296 +}
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/masterfs.solaris/build.xml	Tue Dec 27 19:31:42 2011 +0100
    15.3 @@ -0,0 +1,5 @@
    15.4 +<?xml version="1.0" encoding="UTF-8"?>
    15.5 +<project basedir="." default="netbeans" name="masterfs.solaris">
    15.6 +    <description>Builds, tests, and runs the project org.netbeans.modules.masterfs.solaris</description>
    15.7 +    <import file="../nbbuild/templates/projectized.xml"/>
    15.8 +</project>
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/masterfs.solaris/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
    16.3 @@ -0,0 +1,6 @@
    16.4 +Manifest-Version: 1.0
    16.5 +OpenIDE-Module: org.netbeans.modules.masterfs.solaris
    16.6 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/watcher/solaris/Bundle.properties
    16.7 +OpenIDE-Module-Specification-Version: 1.0
    16.8 +OpenIDE-Module-Requires: org.openide.modules.os.Solaris
    16.9 +OpenIDE-Module-Provides: org.netbeans.modules.masterfs.providers.Notifier
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/masterfs.solaris/nbproject/project.properties	Tue Dec 27 19:31:42 2011 +0100
    17.3 @@ -0,0 +1,3 @@
    17.4 +is.autoload=true
    17.5 +javac.source=1.6
    17.6 +javac.compilerargs=-Xlint -Xlint:-serial
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/masterfs.solaris/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
    18.3 @@ -0,0 +1,35 @@
    18.4 +<?xml version="1.0" encoding="UTF-8"?>
    18.5 +<project xmlns="http://www.netbeans.org/ns/project/1">
    18.6 +    <type>org.netbeans.modules.apisupport.project</type>
    18.7 +    <configuration>
    18.8 +        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
    18.9 +            <code-name-base>org.netbeans.modules.masterfs.solaris</code-name-base>
   18.10 +            <module-dependencies>
   18.11 +                <dependency>
   18.12 +                    <code-name-base>org.netbeans.libs.jna</code-name-base>
   18.13 +                    <build-prerequisite/>
   18.14 +                    <compile-dependency/>
   18.15 +                    <run-dependency>
   18.16 +                        <release-version>1</release-version>
   18.17 +                        <specification-version>1.18</specification-version>
   18.18 +                    </run-dependency>
   18.19 +                </dependency>
   18.20 +                <dependency>
   18.21 +                    <code-name-base>org.netbeans.modules.masterfs</code-name-base>
   18.22 +                    <build-prerequisite/>
   18.23 +                    <compile-dependency/>
   18.24 +                    <run-dependency>
   18.25 +                        <release-version>2</release-version>
   18.26 +                        <specification-version>2.36</specification-version>
   18.27 +                    </run-dependency>
   18.28 +                </dependency>
   18.29 +                <dependency>
   18.30 +                    <code-name-base>org.openide.util.lookup</code-name-base>
   18.31 +                    <build-prerequisite/>
   18.32 +                    <compile-dependency/>
   18.33 +                </dependency>
   18.34 +            </module-dependencies>
   18.35 +            <public-packages/>
   18.36 +        </data>
   18.37 +    </configuration>
   18.38 +</project>
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/masterfs.solaris/src/org/netbeans/modules/masterfs/watcher/solaris/Bundle.properties	Tue Dec 27 19:31:42 2011 +0100
    19.3 @@ -0,0 +1,1 @@
    19.4 +OpenIDE-Module-Name=Master Filesystem @ Solaris
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/masterfs.solaris/src/org/netbeans/modules/masterfs/watcher/solaris/FAMNotifier.java	Tue Dec 27 19:31:42 2011 +0100
    20.3 @@ -0,0 +1,244 @@
    20.4 +/*
    20.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    20.6 + *
    20.7 + * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
    20.8 + *
    20.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   20.10 + * Other names may be trademarks of their respective owners.
   20.11 + *
   20.12 + * The contents of this file are subject to the terms of either the GNU
   20.13 + * General Public License Version 2 only ("GPL") or the Common
   20.14 + * Development and Distribution License("CDDL") (collectively, the
   20.15 + * "License"). You may not use this file except in compliance with the
   20.16 + * License. You can obtain a copy of the License at
   20.17 + * http://www.netbeans.org/cddl-gplv2.html
   20.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   20.19 + * specific language governing permissions and limitations under the
   20.20 + * License.  When distributing the software, include this License Header
   20.21 + * Notice in each file and include the License file at
   20.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   20.23 + * particular file as subject to the "Classpath" exception as provided
   20.24 + * by Oracle in the GPL Version 2 section of the License file that
   20.25 + * accompanied this code. If applicable, add the following below the
   20.26 + * License Header, with the fields enclosed by brackets [] replaced by
   20.27 + * your own identifying information:
   20.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   20.29 + *
   20.30 + * If you wish your version of this file to be governed by only the CDDL
   20.31 + * or only the GPL Version 2, indicate your decision by adding
   20.32 + * "[Contributor] elects to include this software in this distribution
   20.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   20.34 + * single choice of license, a recipient has the option to distribute
   20.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   20.36 + * to extend the choice of license to its licensees as provided above.
   20.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   20.38 + * Version 2 license, then the option applies only if the new code is
   20.39 + * made subject to such option by the copyright holder.
   20.40 + *
   20.41 + * Contributor(s):
   20.42 + *
   20.43 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
   20.44 + */
   20.45 +
   20.46 +package org.netbeans.modules.masterfs.watcher.solaris;
   20.47 +
   20.48 +import org.netbeans.modules.masterfs.providers.Notifier;
   20.49 +import com.sun.jna.Library;
   20.50 +import com.sun.jna.Native;
   20.51 +import com.sun.jna.Pointer;
   20.52 +import com.sun.jna.Structure;
   20.53 +import com.sun.jna.Structure.ByReference;
   20.54 +import java.io.IOException;
   20.55 +import java.util.Collections;
   20.56 +import java.util.HashMap;
   20.57 +import java.util.HashSet;
   20.58 +import java.util.Map;
   20.59 +import java.util.Set;
   20.60 +import java.util.concurrent.BlockingQueue;
   20.61 +import java.util.concurrent.LinkedBlockingQueue;
   20.62 +import java.util.logging.Level;
   20.63 +import java.util.logging.Logger;
   20.64 +import org.openide.util.lookup.ServiceProvider;
   20.65 +
   20.66 +/**
   20.67 + * Notifier implementation using fam library
   20.68 + *
   20.69 + * @author Egor Ushakov
   20.70 + */
   20.71 +@ServiceProvider(service=Notifier.class, position=1000)
   20.72 +public final class FAMNotifier extends Notifier<Integer> {
   20.73 +    private final FAMLibrary.FAMConnection conn;
   20.74 +    private final FAMLibrary lib;
   20.75 +    private final Map<Integer, String> map = Collections.synchronizedMap(new HashMap<Integer, String>());
   20.76 +
   20.77 +    private final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
   20.78 +    private final Thread eventReader;
   20.79 +    private volatile boolean stopped = false;
   20.80 +            
   20.81 +    // limit unanswered requests to avoid FAM hang, see IZ 199497
   20.82 +    private static int ACTIVE_REQUESTS_LIMIT = 100;
   20.83 +    private final Set<Integer> activeRequests = Collections.synchronizedSet(new HashSet<Integer>());
   20.84 +    
   20.85 +    private static final Logger LOG = Logger.getLogger(FAMNotifier.class.getName());
   20.86 +
   20.87 +    public FAMNotifier() {
   20.88 +        if (Boolean.getBoolean("org.netbeans.modules.masterfs.watcher.FAM")) {
   20.89 +            conn = null;
   20.90 +            lib = null;
   20.91 +            eventReader = null;
   20.92 +            return;
   20.93 +        }
   20.94 +        FAMLibrary library;
   20.95 +        try {
   20.96 +            // first try gamin
   20.97 +            library = (FAMLibrary) Native.loadLibrary("gamin-1", FAMLibrary.class); //NOI18N
   20.98 +        } catch (LinkageError x) {
   20.99 +            // then fam
  20.100 +            library = (FAMLibrary) Native.loadLibrary("fam", FAMLibrary.class); //NOI18N
  20.101 +        }
  20.102 +        this.lib = library;
  20.103 +        this.conn = new FAMLibrary.FAMConnection();
  20.104 +        eventReader = new Thread(new Runnable() {
  20.105 +            @Override
  20.106 +            public void run() {
  20.107 +                while (!stopped) {
  20.108 +                    while (lib.FAMPending(conn) > 0) {
  20.109 +                        FAMLibrary.FAMEvent evt = new FAMLibrary.FAMEvent();
  20.110 +                        if (lib.FAMNextEvent(conn, evt) != -1) {
  20.111 +                            if (evt.code == FAMLibrary.FAMEndExist || evt.code == FAMLibrary.FAMAcknowledge) {
  20.112 +                                synchronized (activeRequests) {
  20.113 +                                    activeRequests.remove(evt.fr.reqnum);
  20.114 +                                    activeRequests.notifyAll();
  20.115 +                                }
  20.116 +                            }
  20.117 +                            String path = map.get(evt.fr.reqnum);
  20.118 +                            if (path != null) {
  20.119 +                                events.add(path);
  20.120 +                            }
  20.121 +                        }
  20.122 +                    }
  20.123 +                    // now sleep
  20.124 +                    try {
  20.125 +                        Thread.sleep(1000);
  20.126 +                    } catch (InterruptedException ex) {
  20.127 +                    }
  20.128 +                }
  20.129 +            }
  20.130 +        }, "FAM events reader"); //NOI18N
  20.131 +    }
  20.132 +
  20.133 +    @Override
  20.134 +    public Integer addWatch(String path) throws IOException {
  20.135 +        startRequest();
  20.136 +        FAMLibrary.FAMRequest request = new FAMLibrary.FAMRequest();
  20.137 +        lib.FAMMonitorDirectory(conn, path, request, null);
  20.138 +        LOG.log(Level.FINEST, "addWatch {0}({1}), queue length: {2}", new Object[]{path, request.reqnum, activeRequests.size()});
  20.139 +        activeRequests.add(request.reqnum);
  20.140 +        map.put(request.reqnum, path);
  20.141 +        return request.reqnum;
  20.142 +    }
  20.143 +    
  20.144 +    private void startRequest() {
  20.145 +        synchronized (activeRequests) {
  20.146 +            while (activeRequests.size() > ACTIVE_REQUESTS_LIMIT) {
  20.147 +                long start = System.currentTimeMillis();
  20.148 +                try {
  20.149 +                    activeRequests.wait();
  20.150 +                } catch (InterruptedException ex) {
  20.151 +                }
  20.152 +                LOG.warning("Blocking FAM requests for " + (System.currentTimeMillis()-start) + "ms, requests queue is full"); //NOI18N
  20.153 +            }
  20.154 +        }
  20.155 +    }
  20.156 +    
  20.157 +    @Override
  20.158 +    public String nextEvent() throws IOException, InterruptedException {
  20.159 +        return events.take();
  20.160 +    }
  20.161 +
  20.162 +    @Override
  20.163 +    public void removeWatch(Integer key) throws IOException {
  20.164 +        LOG.log(Level.FINEST, "removeWatch {0}({1}), queue length: {2}", new Object[]{map.get(key), key, activeRequests.size()});
  20.165 +        startRequest();
  20.166 +        activeRequests.add(key);
  20.167 +        lib.FAMCancelMonitor(conn, new FAMLibrary.FAMRequest(key));
  20.168 +        map.remove(key);
  20.169 +    }
  20.170 +
  20.171 +    @Override
  20.172 +    protected void start() throws IOException {
  20.173 +        if (lib == null || lib.FAMOpen(conn) != 0) {
  20.174 +            throw new IOException();
  20.175 +        }
  20.176 +        eventReader.start();
  20.177 +    }
  20.178 +    
  20.179 +    @Override
  20.180 +    protected void stop() throws IOException {
  20.181 +        stopped = true;
  20.182 +        try {
  20.183 +            eventReader.join(2000);
  20.184 +        } catch (InterruptedException ex) {
  20.185 +        }
  20.186 +        lib.FAMClose(conn);
  20.187 +    }
  20.188 +
  20.189 +    interface FAMLibrary extends Library {
  20.190 +        static class FAMConnection extends Structure {
  20.191 +            public static class Reference extends FAMConnection implements ByReference {}
  20.192 +            public int fd;
  20.193 +            public Pointer client;
  20.194 +        }
  20.195 +
  20.196 +        static class FAMRequest extends Structure {
  20.197 +
  20.198 +            public FAMRequest() {
  20.199 +                super();
  20.200 +            }
  20.201 +
  20.202 +            public FAMRequest(int reqnum) {
  20.203 +                super();
  20.204 +                this.reqnum = reqnum;
  20.205 +            }
  20.206 +
  20.207 +            public int reqnum;
  20.208 +        };
  20.209 +
  20.210 +        public static final int PATH_MAX = 1024;
  20.211 +
  20.212 +        public static final int FAMChanged = 1;
  20.213 +        public static final int FAMDeleted = 2;
  20.214 +        public static final int FAMStartExecuting = 3;
  20.215 +        public static final int FAMStopExecuting = 4;
  20.216 +        public static final int FAMCreated = 5;
  20.217 +        public static final int FAMMoved = 6;
  20.218 +        public static final int FAMAcknowledge = 7;
  20.219 +        public static final int FAMExists = 8;
  20.220 +        public static final int FAMEndExist = 9;
  20.221 +
  20.222 +        static class FAMEvent extends Structure {
  20.223 +            public FAMConnection.Reference fc;         /* The fam connection that event occurred on */
  20.224 +            public FAMRequest fr;             /* Corresponds to the FamRequest from monitor */
  20.225 +            public String hostname;            /* host and filename - pointer to which */
  20.226 +            public byte[] filename = new byte[PATH_MAX];   /* file changed */
  20.227 +            public Pointer userdata;            /* userdata associated with this monitor req. */
  20.228 +            public int code;             /* What happened to file - see above */
  20.229 +        }
  20.230 +
  20.231 +
  20.232 +        int FAMOpen(FAMConnection fc);
  20.233 +        int FAMClose(FAMConnection fc);
  20.234 +
  20.235 +        int FAMMonitorDirectory(FAMConnection fc,
  20.236 +                                String filename,
  20.237 +                                FAMRequest fr,
  20.238 +                                Pointer userData);
  20.239 +        int FAMCancelMonitor(FAMConnection fc,
  20.240 +				 FAMRequest fr);
  20.241 +
  20.242 +        int FAMPending(FAMConnection fc);
  20.243 +        int FAMNextEvent(FAMConnection fc,
  20.244 +                         FAMEvent fe);
  20.245 +    }
  20.246 +
  20.247 +}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/masterfs.windows/build.xml	Tue Dec 27 19:31:42 2011 +0100
    21.3 @@ -0,0 +1,5 @@
    21.4 +<?xml version="1.0" encoding="UTF-8"?>
    21.5 +<project basedir="." default="netbeans" name="masterfs.windows">
    21.6 +    <description>Builds, tests, and runs the project org.netbeans.modules.masterfs.windows</description>
    21.7 +    <import file="../nbbuild/templates/projectized.xml"/>
    21.8 +</project>
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/masterfs.windows/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
    22.3 @@ -0,0 +1,7 @@
    22.4 +Manifest-Version: 1.0
    22.5 +OpenIDE-Module: org.netbeans.modules.masterfs.windows
    22.6 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/watcher/windows/Bundle.properties
    22.7 +OpenIDE-Module-Requires: org.openide.modules.os.Windows
    22.8 +OpenIDE-Module-Provides: org.netbeans.modules.masterfs.providers.Notifier
    22.9 +OpenIDE-Module-Specification-Version: 1.0
   22.10 +
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/masterfs.windows/nbproject/project.properties	Tue Dec 27 19:31:42 2011 +0100
    23.3 @@ -0,0 +1,3 @@
    23.4 +is.autoload=true
    23.5 +javac.source=1.6
    23.6 +javac.compilerargs=-Xlint -Xlint:-serial
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/masterfs.windows/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
    24.3 @@ -0,0 +1,35 @@
    24.4 +<?xml version="1.0" encoding="UTF-8"?>
    24.5 +<project xmlns="http://www.netbeans.org/ns/project/1">
    24.6 +    <type>org.netbeans.modules.apisupport.project</type>
    24.7 +    <configuration>
    24.8 +        <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
    24.9 +            <code-name-base>org.netbeans.modules.masterfs.windows</code-name-base>
   24.10 +            <module-dependencies>
   24.11 +                <dependency>
   24.12 +                    <code-name-base>org.netbeans.libs.jna</code-name-base>
   24.13 +                    <build-prerequisite/>
   24.14 +                    <compile-dependency/>
   24.15 +                    <run-dependency>
   24.16 +                        <release-version>1</release-version>
   24.17 +                        <specification-version>1.18</specification-version>
   24.18 +                    </run-dependency>
   24.19 +                </dependency>
   24.20 +                <dependency>
   24.21 +                    <code-name-base>org.netbeans.modules.masterfs</code-name-base>
   24.22 +                    <build-prerequisite/>
   24.23 +                    <compile-dependency/>
   24.24 +                    <run-dependency>
   24.25 +                        <release-version>2</release-version>
   24.26 +                        <specification-version>2.36</specification-version>
   24.27 +                    </run-dependency>
   24.28 +                </dependency>
   24.29 +                <dependency>
   24.30 +                    <code-name-base>org.openide.util.lookup</code-name-base>
   24.31 +                    <build-prerequisite/>
   24.32 +                    <compile-dependency/>
   24.33 +                </dependency>
   24.34 +            </module-dependencies>
   24.35 +            <public-packages/>
   24.36 +        </data>
   24.37 +    </configuration>
   24.38 +</project>
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/masterfs.windows/src/org/netbeans/modules/masterfs/watcher/windows/Bundle.properties	Tue Dec 27 19:31:42 2011 +0100
    25.3 @@ -0,0 +1,1 @@
    25.4 +OpenIDE-Module-Name=Master Filesystem @ Windows
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/masterfs.windows/src/org/netbeans/modules/masterfs/watcher/windows/WindowsNotifier.java	Tue Dec 27 19:31:42 2011 +0100
    26.3 @@ -0,0 +1,422 @@
    26.4 +/*
    26.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    26.6 + *
    26.7 + * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    26.8 + *
    26.9 + * The contents of this file are subject to the terms of either the GNU
   26.10 + * General Public License Version 2 only ("GPL") or the Common
   26.11 + * Development and Distribution License("CDDL") (collectively, the
   26.12 + * "License"). You may not use this file except in compliance with the
   26.13 + * License. You can obtain a copy of the License at
   26.14 + * http://www.netbeans.org/cddl-gplv2.html
   26.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   26.16 + * specific language governing permissions and limitations under the
   26.17 + * License.  When distributing the software, include this License Header
   26.18 + * Notice in each file and include the License file at
   26.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   26.20 + * particular file as subject to the "Classpath" exception as provided
   26.21 + * by Sun in the GPL Version 2 section of the License file that
   26.22 + * accompanied this code. If applicable, add the following below the
   26.23 + * License Header, with the fields enclosed by brackets [] replaced by
   26.24 + * your own identifying information:
   26.25 + * "Portions Copyrighted [year] [name of copyright owner]"
   26.26 + *
   26.27 + * If you wish your version of this file to be governed by only the CDDL
   26.28 + * or only the GPL Version 2, indicate your decision by adding
   26.29 + * "[Contributor] elects to include this software in this distribution
   26.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   26.31 + * single choice of license, a recipient has the option to distribute
   26.32 + * your version of this file under either the CDDL, the GPL Version 2 or
   26.33 + * to extend the choice of license to its licensees as provided above.
   26.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   26.35 + * Version 2 license, then the option applies only if the new code is
   26.36 + * made subject to such option by the copyright holder.
   26.37 + *
   26.38 + * Contributor(s):
   26.39 + *
   26.40 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
   26.41 + */
   26.42 +
   26.43 +package org.netbeans.modules.masterfs.watcher.windows;
   26.44 +
   26.45 +import org.netbeans.modules.masterfs.providers.Notifier;
   26.46 +import com.sun.jna.FromNativeContext;
   26.47 +import com.sun.jna.IntegerType;
   26.48 +import com.sun.jna.Library;
   26.49 +import com.sun.jna.Native;
   26.50 +import com.sun.jna.Pointer;
   26.51 +import com.sun.jna.PointerType;
   26.52 +import com.sun.jna.Structure;
   26.53 +import com.sun.jna.ptr.ByReference;
   26.54 +import java.io.File;
   26.55 +import java.io.IOException;
   26.56 +import java.util.HashMap;
   26.57 +import java.util.Map;
   26.58 +
   26.59 +import com.sun.jna.ptr.IntByReference;
   26.60 +import com.sun.jna.ptr.PointerByReference;
   26.61 +import com.sun.jna.win32.StdCallLibrary;
   26.62 +import com.sun.jna.win32.W32APIFunctionMapper;
   26.63 +import com.sun.jna.win32.W32APITypeMapper;
   26.64 +import java.io.InterruptedIOException;
   26.65 +import java.util.concurrent.BlockingQueue;
   26.66 +import java.util.concurrent.LinkedBlockingQueue;
   26.67 +import java.util.logging.Level;
   26.68 +import java.util.logging.Logger;
   26.69 +import org.openide.util.lookup.ServiceProvider;
   26.70 +
   26.71 +/**
   26.72 + * A {@link Notifier} implementation using Win32 API ReadDirectoryChangesW.
   26.73 + * Based on JNA examples and platform library stubs.
   26.74 + *
   26.75 + * @author nenik
   26.76 + */
   26.77 +@ServiceProvider(service=Notifier.class, position=100)
   26.78 +public final class WindowsNotifier extends Notifier<Void> {
   26.79 +    static final Logger LOG = Logger.getLogger(WindowsNotifier.class.getName());
   26.80 +
   26.81 +    public static final class HANDLE extends PointerType {
   26.82 +        private boolean immutable;
   26.83 +        public HANDLE() { }
   26.84 +        public HANDLE(Pointer p) {
   26.85 +            setPointer(p);
   26.86 +            immutable = true;
   26.87 +        }
   26.88 +
   26.89 +        /** Override to the appropriate object for INVALID_HANDLE_VALUE. */
   26.90 +        @Override
   26.91 +        public Object fromNative(Object nativeValue, FromNativeContext context) {
   26.92 +            Object o = super.fromNative(nativeValue, context);
   26.93 +            if (INVALID_HANDLE_VALUE.equals(o))
   26.94 +                return INVALID_HANDLE_VALUE;
   26.95 +            return o;
   26.96 +        }
   26.97 +
   26.98 +        @Override
   26.99 +        public void setPointer(Pointer p) {
  26.100 +            if (immutable) {
  26.101 +                throw new UnsupportedOperationException("immutable reference");
  26.102 +            }
  26.103 +
  26.104 +            super.setPointer(p);
  26.105 +        }
  26.106 +    }
  26.107 +
  26.108 +    public static class ULONG_PTR extends IntegerType {
  26.109 +        public ULONG_PTR() {
  26.110 +                this(0);
  26.111 +        }
  26.112 +
  26.113 +        public ULONG_PTR(long value) {
  26.114 +                super(Pointer.SIZE, value);
  26.115 +        }
  26.116 +    }
  26.117 +
  26.118 +    public static class OVERLAPPED extends Structure {
  26.119 +        public ULONG_PTR Internal;
  26.120 +        public ULONG_PTR InternalHigh;
  26.121 +        public int Offset;
  26.122 +        public int OffsetHigh;
  26.123 +        public HANDLE hEvent;
  26.124 +    }
  26.125 +
  26.126 +    public static HANDLE INVALID_HANDLE_VALUE = new HANDLE(Pointer.createConstant(
  26.127 +    		Pointer.SIZE == 8 ? -1 : 0xFFFFFFFFL));
  26.128 +
  26.129 +
  26.130 +
  26.131 +    public static class HANDLEByReference extends ByReference {
  26.132 +
  26.133 +    	public HANDLEByReference() {
  26.134 +            this(null);
  26.135 +        }
  26.136 +
  26.137 +        public HANDLEByReference(HANDLE h) {
  26.138 +            super(Pointer.SIZE);
  26.139 +            setValue(h);
  26.140 +        }
  26.141 +
  26.142 +        public void setValue(HANDLE h) {
  26.143 +            getPointer().setPointer(0, h != null ? h.getPointer() : null);
  26.144 +        }
  26.145 +
  26.146 +        public HANDLE getValue() {
  26.147 +            Pointer p = getPointer().getPointer(0);
  26.148 +            if (p == null)
  26.149 +                return null;
  26.150 +            if (INVALID_HANDLE_VALUE.getPointer().equals(p))
  26.151 +                return INVALID_HANDLE_VALUE;
  26.152 +            HANDLE h = new HANDLE();
  26.153 +            h.setPointer(p);
  26.154 +            return h;
  26.155 +        }
  26.156 +    }
  26.157 +
  26.158 +    public static class FILE_NOTIFY_INFORMATION extends Structure {
  26.159 +        public int NextEntryOffset;
  26.160 +        public int Action;
  26.161 +        public int FileNameLength;
  26.162 +        // filename is not nul-terminated, so we can't use a String/WString
  26.163 +        public char[] FileName = new char[1];
  26.164 +
  26.165 +        private FILE_NOTIFY_INFORMATION() {}
  26.166 +
  26.167 +        public FILE_NOTIFY_INFORMATION(int size) {
  26.168 +            if (size < size()) {
  26.169 +               throw new IllegalArgumentException("Size must greater than "
  26.170 +                               + size() + ", requested " + size);
  26.171 +            }
  26.172 +            allocateMemory(size);
  26.173 +        }
  26.174 +
  26.175 +        /** WARNING: this filename may be either the short or long form of the filename. */
  26.176 +        public String getFilename() {
  26.177 +            return new String(FileName, 0, FileNameLength/2);
  26.178 +        }
  26.179 +
  26.180 +        @Override
  26.181 +        public void read() {
  26.182 +            // avoid reading filename until we know how long it is
  26.183 +            FileName = new char[0];
  26.184 +            super.read();
  26.185 +            FileName = getPointer().getCharArray(12, FileNameLength/2);
  26.186 +        }
  26.187 +
  26.188 +        public FILE_NOTIFY_INFORMATION next() {
  26.189 +            if (NextEntryOffset == 0)
  26.190 +                    return null;
  26.191 +            FILE_NOTIFY_INFORMATION next = new FILE_NOTIFY_INFORMATION();
  26.192 +            next.useMemory(getPointer(), NextEntryOffset);
  26.193 +            next.read();
  26.194 +            return next;
  26.195 +        }
  26.196 +    }
  26.197 +
  26.198 +    public static class SECURITY_ATTRIBUTES extends Structure {
  26.199 +        public final int nLength = size();
  26.200 +        public Pointer lpSecurityDescriptor;
  26.201 +        public boolean bInheritHandle;
  26.202 +    }
  26.203 +
  26.204 +    interface Kernel32 extends StdCallLibrary {
  26.205 +        HANDLE CreateFile(String lpFileName, int dwDesiredAccess, int dwShareMode,
  26.206 +                    SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition,
  26.207 +                    int dwFlagsAndAttributes, HANDLE hTemplateFile);
  26.208 +
  26.209 +        HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
  26.210 +                    Pointer CompletionKey, int NumberOfConcurrentThreads);
  26.211 +
  26.212 +        int GetLastError();
  26.213 +
  26.214 +        boolean GetQueuedCompletionStatus(HANDLE CompletionPort,
  26.215 +                    IntByReference lpNumberOfBytes, ByReference lpCompletionKey,
  26.216 +                    PointerByReference lpOverlapped, int dwMilliseconds);
  26.217 +       
  26.218 +        boolean PostQueuedCompletionStatus(HANDLE CompletionPort,
  26.219 +                    int dwNumberOfBytesTransferred, Pointer dwCompletionKey,
  26.220 +                    OVERLAPPED lpOverlapped);
  26.221 +       
  26.222 +        boolean CloseHandle(HANDLE hObject);
  26.223 +
  26.224 +        interface OVERLAPPED_COMPLETION_ROUTINE extends StdCallCallback {
  26.225 +            void callback(int errorCode, int nBytesTransferred,
  26.226 +                            OVERLAPPED overlapped);
  26.227 +        }
  26.228 +
  26.229 +      
  26.230 +        public boolean ReadDirectoryChangesW(HANDLE directory,
  26.231 +                    FILE_NOTIFY_INFORMATION info, int length, boolean watchSubtree,
  26.232 +                int notifyFilter, IntByReference bytesReturned, OVERLAPPED overlapped,
  26.233 +                OVERLAPPED_COMPLETION_ROUTINE completionRoutine);
  26.234 +
  26.235 +
  26.236 +    }
  26.237 +
  26.238 +    final static Kernel32 KERNEL32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class,
  26.239 +            new HashMap() {{
  26.240 +                put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
  26.241 +                put(Library.OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
  26.242 +            }});
  26.243 +
  26.244 +
  26.245 +
  26.246 +    public WindowsNotifier() { // prepare port, start thread?
  26.247 +    }
  26.248 +
  26.249 +    public @Override void removeWatch(Void key) throws IOException {}
  26.250 +
  26.251 +
  26.252 +    public @Override String nextEvent() throws IOException, InterruptedException {
  26.253 +        return events.take();
  26.254 +    }
  26.255 +
  26.256 +    public static final int INFINITE = 0xFFFFFFFF;
  26.257 +
  26.258 +    public static final int FILE_NOTIFY_CHANGE_NAME = 0x00000003;
  26.259 +    public static final int FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
  26.260 +    public static final int FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
  26.261 +    public static final int FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
  26.262 +    public static final int FILE_NOTIFY_CHANGE_CREATION = 0x00000040;
  26.263 +    public static final int FILE_NOTIFY_CHANGE_SECURITY = 0x00000100;
  26.264 +
  26.265 +    private static final int NOTIFY_MASK =
  26.266 +            FILE_NOTIFY_CHANGE_NAME |
  26.267 +            FILE_NOTIFY_CHANGE_ATTRIBUTES |
  26.268 +            FILE_NOTIFY_CHANGE_SIZE |
  26.269 +            FILE_NOTIFY_CHANGE_LAST_WRITE |
  26.270 +            FILE_NOTIFY_CHANGE_CREATION |
  26.271 +            FILE_NOTIFY_CHANGE_SECURITY;
  26.272 +
  26.273 +    public static final int FILE_LIST_DIRECTORY = 0x00000001;
  26.274 +    public static final int OPEN_EXISTING =      3;
  26.275 +
  26.276 +    public static final int FILE_SHARE_READ  = 0x00000001;
  26.277 +    public static final int FILE_SHARE_WRITE = 0x00000002;
  26.278 +    public static final int FILE_SHARE_DELETE = 0x00000004;
  26.279 +
  26.280 +    public static final int FILE_FLAG_OVERLAPPED =           0x40000000;
  26.281 +    public static final int FILE_FLAG_BACKUP_SEMANTICS =     0x02000000;
  26.282 +
  26.283 +    private class FileInfo {
  26.284 +        public final String path;
  26.285 +        public final HANDLE handle;
  26.286 +        public final FILE_NOTIFY_INFORMATION info = new FILE_NOTIFY_INFORMATION(BUFFER_SIZE);
  26.287 +        public final IntByReference infoLength = new IntByReference();
  26.288 +        public final OVERLAPPED overlapped = new OVERLAPPED();
  26.289 +        public FileInfo(String path, HANDLE h) {
  26.290 +            this.path = path;
  26.291 +            this.handle = h;
  26.292 +        }
  26.293 +    }
  26.294 +
  26.295 +    private static int watcherThreadID;
  26.296 +    private Thread watcher;
  26.297 +    private HANDLE port;
  26.298 +    private final Map<String, FileInfo> rootMap = new HashMap<String, FileInfo>();
  26.299 +    private final Map<HANDLE, FileInfo> handleMap = new HashMap<HANDLE, FileInfo>();
  26.300 +    private final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
  26.301 +
  26.302 +    public @Override Void addWatch(String path) throws IOException {
  26.303 +
  26.304 +        if (path.length() < 3 ) throw new IOException("wrong path: " + path);
  26.305 +
  26.306 +        String root = path.substring(0, 3).replace('/', '\\');
  26.307 +        if (root.charAt(1) != ':' || root.charAt(2) != '\\') {
  26.308 +            throw new IOException("wrong path: " + path);
  26.309 +        }
  26.310 +
  26.311 +        if (rootMap.containsKey(root)) return null; // already listening
  26.312 +        path = root; // listen once on the rootpath instead
  26.313 +
  26.314 +        int mask = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
  26.315 +        int flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
  26.316 +        HANDLE handle = KERNEL32.CreateFile(path,
  26.317 +        		FILE_LIST_DIRECTORY,
  26.318 +        		mask, null, OPEN_EXISTING,
  26.319 +                flags, null);
  26.320 +        if (INVALID_HANDLE_VALUE.equals(handle)) {
  26.321 +            throw new IOException("Unable to open " + path + ": "
  26.322 +                                  + KERNEL32.GetLastError());
  26.323 +        }
  26.324 +        FileInfo finfo = new FileInfo(path, handle);
  26.325 +        rootMap.put(path, finfo);
  26.326 +        handleMap.put(handle, finfo);
  26.327 +
  26.328 +        // Existing port is returned
  26.329 +        port = KERNEL32.CreateIoCompletionPort(handle, port, handle.getPointer(), 0);
  26.330 +        if (INVALID_HANDLE_VALUE.equals(port)) {
  26.331 +            throw new IOException("Unable to create/use I/O Completion port "
  26.332 +                    + "for " + path + ": " + KERNEL32.GetLastError());
  26.333 +        }
  26.334 +
  26.335 +        if (!KERNEL32.ReadDirectoryChangesW(handle, finfo.info, finfo.info.size(),
  26.336 +                                        true, NOTIFY_MASK, finfo.infoLength,
  26.337 +                                        finfo.overlapped, null)) {
  26.338 +            int err = KERNEL32.GetLastError();
  26.339 +            throw new IOException("ReadDirectoryChangesW failed on "
  26.340 +                                  + finfo.path + ", handle " + handle
  26.341 +                                  + ": " + err);
  26.342 +        }
  26.343 +        if (watcher == null) {
  26.344 +            Thread t = new Thread("W32 File Monitor") {
  26.345 +                @Override
  26.346 +                public void run() {
  26.347 +                    FileInfo finfo;
  26.348 +                    while (watcher != null) {
  26.349 +                        finfo = waitForChange();
  26.350 +                        if (finfo == null) continue;
  26.351 +
  26.352 +                        try {
  26.353 +                            handleChanges(finfo);
  26.354 +                        } catch(IOException e) {
  26.355 +                            LOG.log(Level.INFO, "handleChanges", e); 
  26.356 +                        }
  26.357 +                    }
  26.358 +                }
  26.359 +            };
  26.360 +            t.setDaemon(true);
  26.361 +            t.start();
  26.362 +            watcher = t;
  26.363 +        }
  26.364 +
  26.365 +        return null;
  26.366 +    }
  26.367 +    
  26.368 +    @Override
  26.369 +    protected void start() throws IOException {
  26.370 +    }
  26.371 +
  26.372 +    @Override
  26.373 +    public void stop() throws IOException {
  26.374 +        try {
  26.375 +            Thread w = watcher;
  26.376 +            if (w == null) {
  26.377 +                return;
  26.378 +            }
  26.379 +            watcher = null;
  26.380 +            w.interrupt();
  26.381 +            w.join(2000);
  26.382 +        } catch (InterruptedException ex) {
  26.383 +            throw (IOException)new InterruptedIOException().initCause(ex);
  26.384 +        }
  26.385 +    }
  26.386 +
  26.387 +    private void notify(File file) {
  26.388 +        events.add(file.getPath());
  26.389 +    }
  26.390 +
  26.391 +
  26.392 +    private static final int BUFFER_SIZE = 4096;
  26.393 +        
  26.394 +    private void handleChanges(FileInfo finfo) throws IOException {
  26.395 +        FILE_NOTIFY_INFORMATION fni = finfo.info;
  26.396 +        // Lazily fetch the data from native to java - asynchronous update
  26.397 +        fni.read();
  26.398 +        do {
  26.399 +            File file = new File(finfo.path, fni.getFilename());
  26.400 +            notify(file);
  26.401 +            
  26.402 +            fni = fni.next();
  26.403 +        } while (fni != null);
  26.404 +        
  26.405 +        if (!KERNEL32.ReadDirectoryChangesW(finfo.handle, finfo.info,
  26.406 +        		finfo.info.size(), true, NOTIFY_MASK,
  26.407 +        		finfo.infoLength, finfo.overlapped, null)) {        	
  26.408 +        		int err = KERNEL32.GetLastError();
  26.409 +                throw new IOException("ReadDirectoryChangesW failed on "
  26.410 +                                  + finfo.path + ": " + err);
  26.411 +        }
  26.412 +    }
  26.413 +
  26.414 +    private FileInfo waitForChange() {
  26.415 +        IntByReference rcount = new IntByReference();
  26.416 +        HANDLEByReference rkey = new HANDLEByReference();
  26.417 +        PointerByReference roverlap = new PointerByReference();
  26.418 +        KERNEL32.GetQueuedCompletionStatus(port, rcount, rkey, roverlap, INFINITE);
  26.419 +        
  26.420 +        synchronized (this) { 
  26.421 +            return (FileInfo)handleMap.get(rkey.getValue());
  26.422 +        }
  26.423 +    }
  26.424 +
  26.425 +}
    27.1 --- a/masterfs/apichanges.xml	Sat Dec 24 00:28:54 2011 +0000
    27.2 +++ b/masterfs/apichanges.xml	Tue Dec 27 19:31:42 2011 +0100
    27.3 @@ -49,6 +49,21 @@
    27.4          <apidef name="masterfs">MasterFileSystem API</apidef>
    27.5      </apidefs>
    27.6      <changes>
    27.7 +        <change id="notifier">
    27.8 +            <api name="masterfs"/>
    27.9 +            <summary>External change notifiers in separate modules</summary>
   27.10 +            <version major="2" minor="36"/>
   27.11 +            <date day="31" month="12" year="2011"/>
   27.12 +            <author login="jtulach"/>
   27.13 +            <compatibility addition="yes" binary="compatible" source="compatible" semantic="compatible" deprecation="no" deletion="no" modification="no"/>
   27.14 +            <description>
   27.15 +                Implementation of <em>native listeners</em> has been moved
   27.16 +                to separate modules, so the masterfs itself does not depend 
   27.17 +                on JNA library.
   27.18 +            </description>
   27.19 +            <class package="org.netbeans.modules.masterfs.providers" name="Notifier"/>
   27.20 +            <issue number="206434"/>
   27.21 +        </change>
   27.22          <change id="canWrite">
   27.23              <api name="masterfs"/>
   27.24              <summary>Determine if a ProvidedExtensions instance should be used to get a files canWrite() value</summary>
    28.1 --- a/masterfs/manifest.mf	Sat Dec 24 00:28:54 2011 +0000
    28.2 +++ b/masterfs/manifest.mf	Tue Dec 27 19:31:42 2011 +0100
    28.3 @@ -2,7 +2,9 @@
    28.4  OpenIDE-Module: org.netbeans.modules.masterfs/2
    28.5  OpenIDE-Module-Install: org/netbeans/modules/masterfs/Installer.class
    28.6  OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/masterfs/resources/Bundle.properties
    28.7 -OpenIDE-Module-Specification-Version: 2.35
    28.8 +OpenIDE-Module-Specification-Version: 2.36
    28.9 +OpenIDE-Module-Recommends: org.netbeans.modules.masterfs.providers.Notifier
   28.10 +OpenIDE-Module-Provides: org.openide.filesystems.FileUtil.toFileObject
   28.11  AutoUpdate-Show-In-Client: false
   28.12  AutoUpdate-Essential-Module: true
   28.13  
    29.1 --- a/masterfs/nbproject/project.xml	Sat Dec 24 00:28:54 2011 +0000
    29.2 +++ b/masterfs/nbproject/project.xml	Tue Dec 27 19:31:42 2011 +0100
    29.3 @@ -50,15 +50,6 @@
    29.4              <code-name-base>org.netbeans.modules.masterfs</code-name-base>
    29.5              <module-dependencies>
    29.6                  <dependency>
    29.7 -                    <code-name-base>org.netbeans.libs.jna</code-name-base>
    29.8 -                    <build-prerequisite/>
    29.9 -                    <compile-dependency/>
   29.10 -                    <run-dependency>
   29.11 -                        <release-version>1</release-version>
   29.12 -                        <specification-version>1.10</specification-version>
   29.13 -                    </run-dependency>
   29.14 -                </dependency>
   29.15 -                <dependency>
   29.16                      <code-name-base>org.netbeans.modules.queries</code-name-base>
   29.17                      <build-prerequisite/>
   29.18                      <compile-dependency/>
   29.19 @@ -139,6 +130,10 @@
   29.20                  <friend>org.netbeans.modules.parsing.api</friend>
   29.21                  <friend>org.netbeans.modules.parsing.lucene</friend>
   29.22                  <friend>org.netbeans.modules.versioning</friend>
   29.23 +                <friend>org.netbeans.modules.masterfs.windows</friend>
   29.24 +                <friend>org.netbeans.modules.masterfs.linux</friend>
   29.25 +                <friend>org.netbeans.modules.masterfs.solaris</friend>
   29.26 +                <friend>org.netbeans.modules.masterfs.macosx</friend>
   29.27                  <package>org.netbeans.modules.masterfs.providers</package>
   29.28              </friend-packages>
   29.29          </data>
    30.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    30.2 +++ b/masterfs/src/org/netbeans/modules/masterfs/providers/Notifier.java	Tue Dec 27 19:31:42 2011 +0100
    30.3 @@ -0,0 +1,131 @@
    30.4 +/*
    30.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    30.6 + *
    30.7 + * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    30.8 + *
    30.9 + * The contents of this file are subject to the terms of either the GNU
   30.10 + * General Public License Version 2 only ("GPL") or the Common
   30.11 + * Development and Distribution License("CDDL") (collectively, the
   30.12 + * "License"). You may not use this file except in compliance with the
   30.13 + * License. You can obtain a copy of the License at
   30.14 + * http://www.netbeans.org/cddl-gplv2.html
   30.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   30.16 + * specific language governing permissions and limitations under the
   30.17 + * License.  When distributing the software, include this License Header
   30.18 + * Notice in each file and include the License file at
   30.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   30.20 + * particular file as subject to the "Classpath" exception as provided
   30.21 + * by Sun in the GPL Version 2 section of the License file that
   30.22 + * accompanied this code. If applicable, add the following below the
   30.23 + * License Header, with the fields enclosed by brackets [] replaced by
   30.24 + * your own identifying information:
   30.25 + * "Portions Copyrighted [year] [name of copyright owner]"
   30.26 + *
   30.27 + * If you wish your version of this file to be governed by only the CDDL
   30.28 + * or only the GPL Version 2, indicate your decision by adding
   30.29 + * "[Contributor] elects to include this software in this distribution
   30.30 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   30.31 + * single choice of license, a recipient has the option to distribute
   30.32 + * your version of this file under either the CDDL, the GPL Version 2 or
   30.33 + * to extend the choice of license to its licensees as provided above.
   30.34 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   30.35 + * Version 2 license, then the option applies only if the new code is
   30.36 + * made subject to such option by the copyright holder.
   30.37 + *
   30.38 + * Contributor(s):
   30.39 + *
   30.40 + * Portions Copyrighted 2010 Sun Microsystems, Inc.
   30.41 + */
   30.42 +
   30.43 +package org.netbeans.modules.masterfs.providers;
   30.44 +
   30.45 +import java.io.IOException;
   30.46 +import org.netbeans.modules.masterfs.watcher.NotifierAccessor;
   30.47 +
   30.48 +/**
   30.49 + * This SPI represents the interface between masterfs and
   30.50 + * different implementations of filesystem watches on various systems.
   30.51 + *
   30.52 + * The SPI is kept very minimal, as the only necessary information is a queue
   30.53 + * of modified folders, the filesystems code will evaluate the nature
   30.54 + * of the change itself.
   30.55 + * The SPI also doesn't distinguish between systems able of hierarchical
   30.56 + * listening and systems without such a capability.
   30.57 + * The implementation can report more events than registered, the infrastructure
   30.58 + * should take care of filtering them.
   30.59 + *
   30.60 + * @author Petr Nejedly
   30.61 + * @since 2.36
   30.62 + */
   30.63 +public abstract class Notifier<KEY> {
   30.64 +    /**
   30.65 +     * Register a path for notifications. Optionally provide a key useful
   30.66 +     * for unregistering the path. The implementations that need to have every
   30.67 +     * path registered individually shall return a valid key, and shall
   30.68 +     * implement the {@link #removeWatch(java.lang.Object)} properly.
   30.69 +     *
   30.70 +     * @param path the path to register for notifications
   30.71 +     * @return a key useful for unregistering the path.
   30.72 +     * @throws IOException if the path can't be registered. For example if the
   30.73 +     * OS limit on the number of watched folders is reached. The exception
   30.74 +     * should be annotated with localized explanation.
   30.75 +     */
   30.76 +    protected abstract KEY addWatch(String path) throws IOException;
   30.77 +    
   30.78 +    /**
   30.79 +     * Unregister a path. Implementations that listen recursively on the whole
   30.80 +     * filesystem may ignore this request. They shall also return
   30.81 +     * <code>null</code> from the {@link #addWatch(java.lang.String)} call.
   30.82 +     * 
   30.83 +     * @param key the key obtained during registration.
   30.84 +     * @throws IOException
   30.85 +     */
   30.86 +    protected abstract void removeWatch(KEY key) throws IOException;
   30.87 +
   30.88 +    /**
   30.89 +     *
   30.90 +     * @return absolute path of the changed folder or null in case
   30.91 +     * of overflow or any other reason to cause a full rescan
   30.92 +     * @throws IOException
   30.93 +     * @throws InterruptedException
   30.94 +     */
   30.95 +    protected abstract String nextEvent() throws IOException, InterruptedException;
   30.96 +    
   30.97 +    /** Starts the notifier. If the implementation is not ready to work,
   30.98 +     * it may throw I/O exception to signal it has not been initialized 
   30.99 +     * properly.
  30.100 +     * @exception IOException if the initialization cannot be performed
  30.101 +     */
  30.102 +    protected abstract void start() throws IOException;
  30.103 +    
  30.104 +    /** Get ready for stop. Clean all resources, the system is about to
  30.105 +     * shutdown the VM. By default this is no-op operation.
  30.106 +     */
  30.107 +    protected void stop() throws IOException {
  30.108 +    }
  30.109 +    
  30.110 +    static {
  30.111 +        NotifierAccessor impl = new NotifierAccessor() {
  30.112 +            @Override
  30.113 +            protected <KEY> KEY addWatch(Notifier<KEY> n, String path) throws IOException {
  30.114 +                return n.addWatch(path);
  30.115 +            }
  30.116 +            @Override
  30.117 +            protected <KEY> void removeWatch(Notifier<KEY> n, KEY key) throws IOException {
  30.118 +                n.removeWatch(key);
  30.119 +            }
  30.120 +            @Override
  30.121 +            protected String nextEvent(Notifier<?> n) throws IOException, InterruptedException {
  30.122 +                return n.nextEvent();
  30.123 +            }
  30.124 +            @Override
  30.125 +            protected void start(Notifier<?> n) throws IOException {
  30.126 +                n.start();
  30.127 +            }
  30.128 +            @Override
  30.129 +            protected void stop(Notifier<?> n) throws IOException {
  30.130 +                n.stop();
  30.131 +            }
  30.132 +        };
  30.133 +    }
  30.134 +}
    31.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/FAMNotifier.java	Sat Dec 24 00:28:54 2011 +0000
    31.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.3 @@ -1,231 +0,0 @@
    31.4 -/*
    31.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    31.6 - *
    31.7 - * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
    31.8 - *
    31.9 - * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   31.10 - * Other names may be trademarks of their respective owners.
   31.11 - *
   31.12 - * The contents of this file are subject to the terms of either the GNU
   31.13 - * General Public License Version 2 only ("GPL") or the Common
   31.14 - * Development and Distribution License("CDDL") (collectively, the
   31.15 - * "License"). You may not use this file except in compliance with the
   31.16 - * License. You can obtain a copy of the License at
   31.17 - * http://www.netbeans.org/cddl-gplv2.html
   31.18 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   31.19 - * specific language governing permissions and limitations under the
   31.20 - * License.  When distributing the software, include this License Header
   31.21 - * Notice in each file and include the License file at
   31.22 - * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   31.23 - * particular file as subject to the "Classpath" exception as provided
   31.24 - * by Oracle in the GPL Version 2 section of the License file that
   31.25 - * accompanied this code. If applicable, add the following below the
   31.26 - * License Header, with the fields enclosed by brackets [] replaced by
   31.27 - * your own identifying information:
   31.28 - * "Portions Copyrighted [year] [name of copyright owner]"
   31.29 - *
   31.30 - * If you wish your version of this file to be governed by only the CDDL
   31.31 - * or only the GPL Version 2, indicate your decision by adding
   31.32 - * "[Contributor] elects to include this software in this distribution
   31.33 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
   31.34 - * single choice of license, a recipient has the option to distribute
   31.35 - * your version of this file under either the CDDL, the GPL Version 2 or
   31.36 - * to extend the choice of license to its licensees as provided above.
   31.37 - * However, if you add GPL Version 2 code and therefore, elected the GPL
   31.38 - * Version 2 license, then the option applies only if the new code is
   31.39 - * made subject to such option by the copyright holder.
   31.40 - *
   31.41 - * Contributor(s):
   31.42 - *
   31.43 - * Portions Copyrighted 2010 Sun Microsystems, Inc.
   31.44 - */
   31.45 -
   31.46 -package org.netbeans.modules.masterfs.watcher;
   31.47 -
   31.48 -import com.sun.jna.Library;
   31.49 -import com.sun.jna.Native;
   31.50 -import com.sun.jna.Pointer;
   31.51 -import com.sun.jna.Structure;
   31.52 -import com.sun.jna.Structure.ByReference;
   31.53 -import java.io.IOException;
   31.54 -import java.util.Collections;
   31.55 -import java.util.HashMap;
   31.56 -import java.util.HashSet;
   31.57 -import java.util.Map;
   31.58 -import java.util.Set;
   31.59 -import java.util.concurrent.BlockingQueue;
   31.60 -import java.util.concurrent.LinkedBlockingQueue;
   31.61 -import java.util.logging.Level;
   31.62 -import java.util.logging.Logger;
   31.63 -
   31.64 -/**
   31.65 - * Notifier implementation using fam library
   31.66 - *
   31.67 - * @author Egor Ushakov
   31.68 - */
   31.69 -public class FAMNotifier extends Notifier<Integer> {
   31.70 -    private final FAMLibrary.FAMConnection conn;
   31.71 -    private final FAMLibrary lib;
   31.72 -    private final Map<Integer, String> map = Collections.synchronizedMap(new HashMap<Integer, String>());
   31.73 -
   31.74 -    private final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
   31.75 -    private final Thread eventReader;
   31.76 -    private volatile boolean stopped = false;
   31.77 -            
   31.78 -    // limit unanswered requests to avoid FAM hang, see IZ 199497
   31.79 -    private static int ACTIVE_REQUESTS_LIMIT = 100;
   31.80 -    private final Set<Integer> activeRequests = Collections.synchronizedSet(new HashSet<Integer>());
   31.81 -    
   31.82 -    private static final Logger LOG = Logger.getLogger(FAMNotifier.class.getName());
   31.83 -
   31.84 -    public FAMNotifier() {
   31.85 -        FAMLibrary library;
   31.86 -        try {
   31.87 -            // first try gamin
   31.88 -            library = (FAMLibrary) Native.loadLibrary("gamin-1", FAMLibrary.class); //NOI18N
   31.89 -        } catch (LinkageError x) {
   31.90 -            // then fam
   31.91 -            library = (FAMLibrary) Native.loadLibrary("fam", FAMLibrary.class); //NOI18N
   31.92 -        }
   31.93 -        this.lib = library;
   31.94 -        this.conn = new FAMLibrary.FAMConnection();
   31.95 -        if (lib.FAMOpen(conn) != 0) {
   31.96 -            throw new IllegalStateException();
   31.97 -        }
   31.98 -        eventReader = new Thread(new Runnable() {
   31.99 -            @Override
  31.100 -            public void run() {
  31.101 -                while (!stopped) {
  31.102 -                    while (lib.FAMPending(conn) > 0) {
  31.103 -                        FAMLibrary.FAMEvent evt = new FAMLibrary.FAMEvent();
  31.104 -                        if (lib.FAMNextEvent(conn, evt) != -1) {
  31.105 -                            if (evt.code == FAMLibrary.FAMEndExist || evt.code == FAMLibrary.FAMAcknowledge) {
  31.106 -                                synchronized (activeRequests) {
  31.107 -                                    activeRequests.remove(evt.fr.reqnum);
  31.108 -                                    activeRequests.notifyAll();
  31.109 -                                }
  31.110 -                            }
  31.111 -                            String path = map.get(evt.fr.reqnum);
  31.112 -                            if (path != null) {
  31.113 -                                events.add(path);
  31.114 -                            }
  31.115 -                        }
  31.116 -                    }
  31.117 -                    // now sleep
  31.118 -                    try {
  31.119 -                        Thread.sleep(1000);
  31.120 -                    } catch (InterruptedException ex) {
  31.121 -                    }
  31.122 -                }
  31.123 -            }
  31.124 -        }, "FAM events reader"); //NOI18N
  31.125 -        eventReader.start();
  31.126 -    }
  31.127 -
  31.128 -    @Override
  31.129 -    public Integer addWatch(String path) throws IOException {
  31.130 -        startRequest();
  31.131 -        FAMLibrary.FAMRequest request = new FAMLibrary.FAMRequest();
  31.132 -        lib.FAMMonitorDirectory(conn, path, request, null);
  31.133 -        LOG.log(Level.FINEST, "addWatch {0}({1}), queue length: {2}", new Object[]{path, request.reqnum, activeRequests.size()});
  31.134 -        activeRequests.add(request.reqnum);
  31.135 -        map.put(request.reqnum, path);
  31.136 -        return request.reqnum;
  31.137 -    }
  31.138 -    
  31.139 -    private void startRequest() {
  31.140 -        synchronized (activeRequests) {
  31.141 -            while (activeRequests.size() > ACTIVE_REQUESTS_LIMIT) {
  31.142 -                long start = System.currentTimeMillis();
  31.143 -                try {
  31.144 -                    activeRequests.wait();
  31.145 -                } catch (InterruptedException ex) {
  31.146 -                }
  31.147 -                LOG.warning("Blocking FAM requests for " + (System.currentTimeMillis()-start) + "ms, requests queue is full"); //NOI18N
  31.148 -            }
  31.149 -        }
  31.150 -    }
  31.151 -    
  31.152 -    @Override
  31.153 -    public String nextEvent() throws IOException, InterruptedException {
  31.154 -        return events.take();
  31.155 -    }
  31.156 -
  31.157 -    @Override
  31.158 -    public void removeWatch(Integer key) throws IOException {
  31.159 -        LOG.log(Level.FINEST, "removeWatch {0}({1}), queue length: {2}", new Object[]{map.get(key), key, activeRequests.size()});
  31.160 -        startRequest();
  31.161 -        activeRequests.add(key);
  31.162 -        lib.FAMCancelMonitor(conn, new FAMLibrary.FAMRequest(key));
  31.163 -        map.remove(key);
  31.164 -    }
  31.165 -
  31.166 -    @Override
  31.167 -    protected void stop() throws IOException {
  31.168 -        stopped = true;
  31.169 -        try {
  31.170 -            eventReader.join(2000);
  31.171 -        } catch (InterruptedException ex) {
  31.172 -        }
  31.173 -        lib.FAMClose(conn);
  31.174 -    }
  31.175 -
  31.176 -    interface FAMLibrary extends Library {
  31.177 -        static class FAMConnection extends Structure {
  31.178 -            public static class Reference extends FAMConnection implements ByReference {}
  31.179 -            public int fd;
  31.180 -            public Pointer client;
  31.181 -        }
  31.182 -
  31.183 -        static class FAMRequest extends Structure {
  31.184 -
  31.185 -            public FAMRequest() {
  31.186 -                super();
  31.187 -            }
  31.188 -
  31.189 -            public FAMRequest(int reqnum) {
  31.190 -                super();
  31.191 -                this.reqnum = reqnum;
  31.192 -            }
  31.193 -
  31.194 -            public int reqnum;
  31.195 -        };
  31.196 -
  31.197 -        public static final int PATH_MAX = 1024;
  31.198 -
  31.199 -        public static final int FAMChanged = 1;
  31.200 -        public static final int FAMDeleted = 2;
  31.201 -        public static final int FAMStartExecuting = 3;
  31.202 -        public static final int FAMStopExecuting = 4;
  31.203 -        public static final int FAMCreated = 5;
  31.204 -        public static final int FAMMoved = 6;
  31.205 -        public static final int FAMAcknowledge = 7;
  31.206 -        public static final int FAMExists = 8;
  31.207 -        public static final int FAMEndExist = 9;
  31.208 -
  31.209 -        static class FAMEvent extends Structure {
  31.210 -            public FAMConnection.Reference fc;         /* The fam connection that event occurred on */
  31.211 -            public FAMRequest fr;             /* Corresponds to the FamRequest from monitor */
  31.212 -            public String hostname;            /* host and filename - pointer to which */
  31.213 -            public byte[] filename = new byte[PATH_MAX];   /* file changed */
  31.214 -            public Pointer userdata;            /* userdata associated with this monitor req. */
  31.215 -            public int code;             /* What happened to file - see above */
  31.216 -        }
  31.217 -
  31.218 -
  31.219 -        int FAMOpen(FAMConnection fc);
  31.220 -        int FAMClose(FAMConnection fc);
  31.221 -
  31.222 -        int FAMMonitorDirectory(FAMConnection fc,
  31.223 -                                String filename,
  31.224 -                                FAMRequest fr,
  31.225 -                                Pointer userData);
  31.226 -        int FAMCancelMonitor(FAMConnection fc,
  31.227 -				 FAMRequest fr);
  31.228 -
  31.229 -        int FAMPending(FAMConnection fc);
  31.230 -        int FAMNextEvent(FAMConnection fc,
  31.231 -                         FAMEvent fe);
  31.232 -    }
  31.233 -
  31.234 -}
    32.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/LinuxNotifier.java	Sat Dec 24 00:28:54 2011 +0000
    32.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.3 @@ -1,209 +0,0 @@
    32.4 -/*
    32.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    32.6 - *
    32.7 - * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    32.8 - *
    32.9 - * The contents of this file are subject to the terms of either the GNU
   32.10 - * General Public License Version 2 only ("GPL") or the Common
   32.11 - * Development and Distribution License("CDDL") (collectively, the
   32.12 - * "License"). You may not use this file except in compliance with the
   32.13 - * License. You can obtain a copy of the License at
   32.14 - * http://www.netbeans.org/cddl-gplv2.html
   32.15 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   32.16 - * specific language governing permissions and limitations under the
   32.17 - * License.  When distributing the software, include this License Header
   32.18 - * Notice in each file and include the License file at
   32.19 - * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   32.20 - * particular file as subject to the "Classpath" exception as provided
   32.21 - * by Sun in the GPL Version 2 section of the License file that
   32.22 - * accompanied this code. If applicable, add the following below the
   32.23 - * License Header, with the fields enclosed by brackets [] replaced by
   32.24 - * your own identifying information:
   32.25 - * "Portions Copyrighted [year] [name of copyright owner]"
   32.26 - *
   32.27 - * If you wish your version of this file to be governed by only the CDDL
   32.28 - * or only the GPL Version 2, indicate your decision by adding
   32.29 - * "[Contributor] elects to include this software in this distribution
   32.30 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
   32.31 - * single choice of license, a recipient has the option to distribute
   32.32 - * your version of this file under either the CDDL, the GPL Version 2 or
   32.33 - * to extend the choice of license to its licensees as provided above.
   32.34 - * However, if you add GPL Version 2 code and therefore, elected the GPL
   32.35 - * Version 2 license, then the option applies only if the new code is
   32.36 - * made subject to such option by the copyright holder.
   32.37 - *
   32.38 - * Contributor(s):
   32.39 - *
   32.40 - * Portions Copyrighted 2010 Sun Microsystems, Inc.
   32.41 - */
   32.42 -
   32.43 -package org.netbeans.modules.masterfs.watcher;
   32.44 -
   32.45 -import com.sun.jna.Library;
   32.46 -import com.sun.jna.Native;
   32.47 -import com.sun.jna.NativeLibrary;
   32.48 -import java.io.IOException;
   32.49 -import java.nio.ByteBuffer;
   32.50 -import java.nio.ByteOrder;
   32.51 -import java.util.HashMap;
   32.52 -import java.util.Map;
   32.53 -import java.util.logging.Level;
   32.54 -import java.util.logging.Logger;
   32.55 -
   32.56 -/**
   32.57 - * A {@link Notifier} implementation based on Linux inotify mechanism.
   32.58 - *
   32.59 - * @author nenik
   32.60 - */
   32.61 -final class LinuxNotifier extends Notifier<LinuxNotifier.LKey> {
   32.62 -    private static final Logger LOG = Logger.getLogger(LinuxNotifier.class.getName());
   32.63 -    
   32.64 -    private static interface InotifyImpl extends Library {
   32.65 -	public int inotify_init();
   32.66 -	public int inotify_init1(int flags);
   32.67 -	public int close(int fd);
   32.68 -    public int read(int fd, ByteBuffer buff, int count);
   32.69 -	public int inotify_add_watch(int fd, String pathname, int mask);
   32.70 -	public int inotify_rm_watch(int fd, int wd);
   32.71 -
   32.72 -        public static final int O_CLOEXEC = 02000000; //0x80000
   32.73 -
   32.74 -        // Masks
   32.75 -        public static final int IN_ACCESS = 0x0001;      /* File was accessed */
   32.76 -        public static final int IN_MODIFY = 0x0002;      /* File was modified */
   32.77 -        public static final int IN_ATTRIB = 0x0004;      /* Metadata changed */
   32.78 -        public static final int IN_CLOSE_WRITE =   0x0008;      /* Writtable file was closed */
   32.79 -        public static final int IN_CLOSE_NOWRITE = 0x0010;      /* Unwrittable file closed */
   32.80 -        public static final int IN_OPEN =          0x0020;      /* File was opened */
   32.81 -        public static final int IN_MOVED_FROM =   0x0040;      /* File was moved from X */
   32.82 -        public static final int IN_MOVED_TO =     0x0080;      /* File was moved to Y */
   32.83 -        public static final int IN_CREATE =        0x0100;      /* Subfile was created */
   32.84 -        public static final int IN_DELETE =        0x0200;      /* Subfile was deleted */
   32.85 -        public static final int IN_DELETE_SELF =    0x0400;      /* Self was deleted */
   32.86 -        public static final int IN_MOVE_SELF =     0x0800;      /* Self was moved */
   32.87 -
   32.88 -        // additional event masks
   32.89 -        public static final int IN_UNMOUNT =      0x2000;      /* Backing fs was unmounted */
   32.90 -        public static final int IN_Q_OVERFLOW =    0x4000;      /* Event queued overflowed */
   32.91 -        public static final int IN_IGNORED =       0x8000;      /* File was ignored */
   32.92 -
   32.93 -    }
   32.94 -
   32.95 -    final InotifyImpl IMPL;
   32.96 -    int fd;
   32.97 -    private ByteBuffer buff = ByteBuffer.allocateDirect(4096);
   32.98 -
   32.99 -    // An array would serve nearly as well
  32.100 -    private Map<Integer, LKey> map = new HashMap<Integer, LKey>();
  32.101 -
  32.102 -    public LinuxNotifier() {
  32.103 -         IMPL = (InotifyImpl) Native.loadLibrary("c", InotifyImpl.class);
  32.104 -         buff.position(buff.capacity()); // make the buffer empty
  32.105 -         buff.order(ByteOrder.nativeOrder());
  32.106 -         fd = IMPL.inotify_init1(InotifyImpl.O_CLOEXEC);
  32.107 -         if (fd < 0) {
  32.108 -             LOG.log(
  32.109 -                 Level.INFO, "Linux kernel {0} returned {1} from inotify_init1",
  32.110 -                 new Object[] { System.getProperty("os.version"), fd }
  32.111 -             );
  32.112 -             fd = IMPL.inotify_init();
  32.113 -             LOG.log(Level.INFO, "Trying inotify_init: {0}", fd);
  32.114 -         }
  32.115 -         if (fd < 0) {
  32.116 -             throw new IllegalStateException("inotify_init failed: " + fd);
  32.117 -         }
  32.118 -    }
  32.119 -
  32.120 -    private String getString(int maxLen) {
  32.121 -        if (maxLen < 1) return null; // no name field
  32.122 -        int stop = maxLen - 1;
  32.123 -        byte[] temp = new byte[maxLen];
  32.124 -        buff.get(temp);
  32.125 -        while (temp[stop] == 0) stop--;
  32.126 -        return new String(temp, 0, stop+1);
  32.127 -    }
  32.128 -
  32.129 -    @Override public String nextEvent() throws IOException {
  32.130 -        /* inotify event structure layout:
  32.131 -         *   int      wd;    // Watch descriptor
  32.132 -         *   uint32_t mask;  // Mask of events
  32.133 -         *   uint32_t cookie;// Unique cookie associating related events (for rename(2))
  32.134 -         *   uint32_t len;   // Size of name field
  32.135 -         *   char     name[];// Optional null-terminated name
  32.136 -         */
  32.137 -        while (buff.remaining() < 16 || buff.remaining() < 16 + buff.getInt(buff.position() + 12)) {
  32.138 -            buff.compact();
  32.139 -            int len = IMPL.read(fd, buff, buff.remaining());
  32.140 -
  32.141 -            if (len <= 0) {
  32.142 -                // lazily get a thread local errno
  32.143 -                int errno = NativeLibrary.getInstance("c").getFunction("errno").getInt(0);
  32.144 -                if (errno == 4) { // EINTR
  32.145 -                    buff.flip();
  32.146 -                    continue; // restart the I/O 
  32.147 -                } else {
  32.148 -                    throw new IOException("error reading from inotify: " + errno);
  32.149 -                }
  32.150 -            }
  32.151 -            buff.position(buff.position() + len);
  32.152 -            buff.flip();
  32.153 -        }
  32.154 -
  32.155 -        // now we have enough data in the buffer
  32.156 -        int wd = buff.getInt();
  32.157 -        int mask = buff.getInt();
  32.158 -        int cookie = buff.getInt();
  32.159 -        int len = buff.getInt();
  32.160 -        String name = getString(len); // ignore
  32.161 -
  32.162 -        LKey key = map.get(wd);
  32.163 -        if (key == null) { /* wd == -1 -> Queue overflow */
  32.164 -            return null;
  32.165 -        }
  32.166 -        
  32.167 -        return key.path;
  32.168 -    }
  32.169 -
  32.170 -
  32.171 -    static class LKey {
  32.172 -        int id;
  32.173 -        String path;
  32.174 -
  32.175 -        public LKey(int id, String path) {
  32.176 -            this.id = id;
  32.177 -            this.path = path;
  32.178 -        }
  32.179 -
  32.180 -        @Override
  32.181 -        public String toString() {
  32.182 -            return "LKey[" + id + " - '" + path + "']";
  32.183 -        }
  32.184 -    }
  32.185 -
  32.186 -    @Override public LKey addWatch(String path) throws IOException {
  32.187 -        // what if the file doesn't exist?
  32.188 -        int id = IMPL.inotify_add_watch(fd, path,
  32.189 -                    InotifyImpl.IN_CREATE | InotifyImpl.IN_MOVED_TO |
  32.190 -                    InotifyImpl.IN_DELETE | InotifyImpl.IN_MOVED_FROM |
  32.191 -                    InotifyImpl.IN_MODIFY | InotifyImpl.IN_ATTRIB);
  32.192 -        //XXX handle error return value (-1)
  32.193 -        LOG.log(Level.FINEST, "addWatch{0} res: {1}", new Object[]{path, id});
  32.194 -        if (id <= 0) {
  32.195 -            // 28 == EINOSPC
  32.196 -            int errno = NativeLibrary.getInstance("c").getFunction("errno").getInt(0); // NOI18N
  32.197 -            throw new IOException("addWatch on " + path + " errno: " + errno); // NOI18N
  32.198 -        }
  32.199 -
  32.200 -        LKey newKey = map.get(id);
  32.201 -        if (newKey == null) {
  32.202 -            newKey = new LKey(id, path);
  32.203 -            map.put(id, newKey);
  32.204 -        }
  32.205 -        return newKey;
  32.206 -    }
  32.207 -
  32.208 -    @Override public void removeWatch(LKey lkey) {
  32.209 -        map.remove(lkey.id);
  32.210 -        IMPL.inotify_rm_watch(fd, lkey.id);
  32.211 -    }
  32.212 -}
    33.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/Notifier.java	Sat Dec 24 00:28:54 2011 +0000
    33.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.3 @@ -1,153 +0,0 @@
    33.4 -/*
    33.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    33.6 - *
    33.7 - * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    33.8 - *
    33.9 - * The contents of this file are subject to the terms of either the GNU
   33.10 - * General Public License Version 2 only ("GPL") or the Common
   33.11 - * Development and Distribution License("CDDL") (collectively, the
   33.12 - * "License"). You may not use this file except in compliance with the
   33.13 - * License. You can obtain a copy of the License at
   33.14 - * http://www.netbeans.org/cddl-gplv2.html
   33.15 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   33.16 - * specific language governing permissions and limitations under the
   33.17 - * License.  When distributing the software, include this License Header
   33.18 - * Notice in each file and include the License file at
   33.19 - * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   33.20 - * particular file as subject to the "Classpath" exception as provided
   33.21 - * by Sun in the GPL Version 2 section of the License file that
   33.22 - * accompanied this code. If applicable, add the following below the
   33.23 - * License Header, with the fields enclosed by brackets [] replaced by
   33.24 - * your own identifying information:
   33.25 - * "Portions Copyrighted [year] [name of copyright owner]"
   33.26 - *
   33.27 - * If you wish your version of this file to be governed by only the CDDL
   33.28 - * or only the GPL Version 2, indicate your decision by adding
   33.29 - * "[Contributor] elects to include this software in this distribution
   33.30 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
   33.31 - * single choice of license, a recipient has the option to distribute
   33.32 - * your version of this file under either the CDDL, the GPL Version 2 or
   33.33 - * to extend the choice of license to its licensees as provided above.
   33.34 - * However, if you add GPL Version 2 code and therefore, elected the GPL
   33.35 - * Version 2 license, then the option applies only if the new code is
   33.36 - * made subject to such option by the copyright holder.
   33.37 - *
   33.38 - * Contributor(s):
   33.39 - *
   33.40 - * Portions Copyrighted 2010 Sun Microsystems, Inc.
   33.41 - */
   33.42 -
   33.43 -package org.netbeans.modules.masterfs.watcher;
   33.44 -
   33.45 -import java.io.IOException;
   33.46 -import java.lang.ref.ReferenceQueue;
   33.47 -import java.lang.ref.WeakReference;
   33.48 -import java.util.logging.Level;
   33.49 -import org.openide.filesystems.FileObject;
   33.50 -import org.openide.util.Exceptions;
   33.51 -import org.openide.util.Utilities;
   33.52 -
   33.53 -/**
   33.54 - * This internal SPI represents the interface between masterfs and
   33.55 - * different implementations of filesystem watches on various systems.
   33.56 - *
   33.57 - * The SPI is kept very minimal, as the only necessary information is a queue
   33.58 - * of modified folders, the filesystems code will evaluate the nature
   33.59 - * of the change itself.
   33.60 - * The SPI also doesn't distinguish between systems able of hierarchical
   33.61 - * listening and systems without such a capability.
   33.62 - * The implementation can report more events than registered, the infrastructure
   33.63 - * should take care of filtering them.
   33.64 - *
   33.65 - * @author nenik
   33.66 - */
   33.67 -public abstract class Notifier<KEY> {
   33.68 -
   33.69 -    /**
   33.70 -     * Register a path for notifications. Optionally provide a key useful
   33.71 -     * for unregistering the path. The implementations that need to have every
   33.72 -     * path registered individually shall return a valid key, and shall
   33.73 -     * implement the {@link #removeWatch(java.lang.Object)} properly.
   33.74 -     *
   33.75 -     * @param path the path to register for notifications
   33.76 -     * @return a key useful for unregistering the path.
   33.77 -     * @throws IOException if the path can't be registered. For example if the
   33.78 -     * OS limit on the number of watched folders is reached. The exception
   33.79 -     * should be annotated with localized explanation.
   33.80 -     */
   33.81 -    public abstract KEY addWatch(String path) throws IOException;
   33.82 -    
   33.83 -    /**
   33.84 -     * Unregister a path. Implementations that listen recursively on the whole
   33.85 -     * filesystem may ignore this request. They shall also return
   33.86 -     * <code>null</code> from the {@link #addWatch(java.lang.String)} call.
   33.87 -     * 
   33.88 -     * @param key the key obtained during registration.
   33.89 -     * @throws IOException
   33.90 -     */
   33.91 -    public abstract void removeWatch(KEY key) throws IOException;
   33.92 -
   33.93 -    /**
   33.94 -     *
   33.95 -     * @return absolute path of the changed folder or null in case
   33.96 -     * of overflow or any other reason to cause a full rescan
   33.97 -     * @throws IOException
   33.98 -     * @throws InterruptedException
   33.99 -     */
  33.100 -    public abstract String nextEvent() throws IOException, InterruptedException;
  33.101 -    
  33.102 -    /** Get ready for stop. Clean all resources, the system is about to
  33.103 -     * shutdown the VM. By default this is no-op operation.
  33.104 -     */
  33.105 -    protected void stop() throws IOException {
  33.106 -    }
  33.107 -    
  33.108 -    class KeyRef extends WeakReference<FileObject> {
  33.109 -        private final KEY key;
  33.110 -        private final int hash;
  33.111 -
  33.112 -        public KeyRef(FileObject fo, KEY key, ReferenceQueue<FileObject> queue) {
  33.113 -            super(fo, queue);
  33.114 -            this.key = key;
  33.115 -            this.hash = fo.hashCode();
  33.116 -            if (key != null) {
  33.117 -                Watcher.LOG.log(Level.FINE, "Adding watch for {0}", key);
  33.118 -            }
  33.119 -        }
  33.120 -
  33.121 -        @Override
  33.122 -        public FileObject get() {
  33.123 -            return super.get();
  33.124 -        }
  33.125 -        
  33.126 -        @Override
  33.127 -        public boolean equals(Object obj) {
  33.128 -            if (obj == this) {
  33.129 -                return true;
  33.130 -            }
  33.131 -            try {
  33.132 -                KeyRef kr = (KeyRef)obj;
  33.133 -                FileObject mine = get();
  33.134 -                FileObject theirs = kr.get();
  33.135 -                if (mine == null) {
  33.136 -                    return theirs == null;
  33.137 -                } else {
  33.138 -                    return mine.equals(theirs);
  33.139 -                }
  33.140 -            } catch (ClassCastException ex) {
  33.141 -                return false;
  33.142 -            }
  33.143 -        }
  33.144 -
  33.145 -        final void removeWatch() throws IOException {
  33.146 -            Watcher.LOG.log(Level.FINE, "Removing watch for {0}", key);
  33.147 -            Notifier.this.removeWatch(key);
  33.148 -        }
  33.149 -
  33.150 -        @Override
  33.151 -        public int hashCode() {
  33.152 -            return hash;
  33.153 -        }
  33.154 -    } // KeyRef
  33.155 -    
  33.156 -}
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/masterfs/src/org/netbeans/modules/masterfs/watcher/NotifierAccessor.java	Tue Dec 27 19:31:42 2011 +0100
    34.3 @@ -0,0 +1,74 @@
    34.4 +/*
    34.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    34.6 + *
    34.7 + * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
    34.8 + *
    34.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   34.10 + * Other names may be trademarks of their respective owners.
   34.11 + *
   34.12 + * The contents of this file are subject to the terms of either the GNU
   34.13 + * General Public License Version 2 only ("GPL") or the Common
   34.14 + * Development and Distribution License("CDDL") (collectively, the
   34.15 + * "License"). You may not use this file except in compliance with the
   34.16 + * License. You can obtain a copy of the License at
   34.17 + * http://www.netbeans.org/cddl-gplv2.html
   34.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   34.19 + * specific language governing permissions and limitations under the
   34.20 + * License.  When distributing the software, include this License Header
   34.21 + * Notice in each file and include the License file at
   34.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   34.23 + * particular file as subject to the "Classpath" exception as provided
   34.24 + * by Oracle in the GPL Version 2 section of the License file that
   34.25 + * accompanied this code. If applicable, add the following below the
   34.26 + * License Header, with the fields enclosed by brackets [] replaced by
   34.27 + * your own identifying information:
   34.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   34.29 + *
   34.30 + * If you wish your version of this file to be governed by only the CDDL
   34.31 + * or only the GPL Version 2, indicate your decision by adding
   34.32 + * "[Contributor] elects to include this software in this distribution
   34.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   34.34 + * single choice of license, a recipient has the option to distribute
   34.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   34.36 + * to extend the choice of license to its licensees as provided above.
   34.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   34.38 + * Version 2 license, then the option applies only if the new code is
   34.39 + * made subject to such option by the copyright holder.
   34.40 + *
   34.41 + * Contributor(s):
   34.42 + *
   34.43 + * Portions Copyrighted 2011 Sun Microsystems, Inc.
   34.44 + */
   34.45 +package org.netbeans.modules.masterfs.watcher;
   34.46 +
   34.47 +import java.io.IOException;
   34.48 +import org.netbeans.modules.masterfs.providers.Notifier;
   34.49 +
   34.50 +/** Access to protected methods of {@link Notifier}.
   34.51 + *
   34.52 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   34.53 + */
   34.54 +public abstract class NotifierAccessor {
   34.55 +    private static NotifierAccessor DEFAULT;
   34.56 +    static {
   34.57 +        try {
   34.58 +            Class.forName(Notifier.class.getName(), true, Notifier.class.getClassLoader());
   34.59 +        } catch (ClassNotFoundException ex) {
   34.60 +            throw new IllegalStateException(ex);
   34.61 +        }
   34.62 +    }
   34.63 +    protected NotifierAccessor() {
   34.64 +        DEFAULT = this;
   34.65 +    }
   34.66 +    
   34.67 +    public static NotifierAccessor getDefault() {
   34.68 +        assert DEFAULT != null;
   34.69 +        return DEFAULT;
   34.70 +    }
   34.71 +    
   34.72 +    protected abstract <KEY> KEY addWatch(Notifier<KEY> n, String path) throws IOException;
   34.73 +    protected abstract <KEY> void removeWatch(Notifier<KEY> n, KEY key) throws IOException;
   34.74 +    protected abstract String nextEvent(Notifier<?> n) throws IOException, InterruptedException;
   34.75 +    protected abstract void start(Notifier<?> n) throws IOException;    
   34.76 +    protected abstract void stop(Notifier<?> n) throws IOException;    
   34.77 +}
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/masterfs/src/org/netbeans/modules/masterfs/watcher/NotifierKeyRef.java	Tue Dec 27 19:31:42 2011 +0100
    35.3 @@ -0,0 +1,104 @@
    35.4 +/*
    35.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    35.6 + *
    35.7 + * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
    35.8 + *
    35.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   35.10 + * Other names may be trademarks of their respective owners.
   35.11 + *
   35.12 + * The contents of this file are subject to the terms of either the GNU
   35.13 + * General Public License Version 2 only ("GPL") or the Common
   35.14 + * Development and Distribution License("CDDL") (collectively, the
   35.15 + * "License"). You may not use this file except in compliance with the
   35.16 + * License. You can obtain a copy of the License at
   35.17 + * http://www.netbeans.org/cddl-gplv2.html
   35.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   35.19 + * specific language governing permissions and limitations under the
   35.20 + * License.  When distributing the software, include this License Header
   35.21 + * Notice in each file and include the License file at
   35.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   35.23 + * particular file as subject to the "Classpath" exception as provided
   35.24 + * by Oracle in the GPL Version 2 section of the License file that
   35.25 + * accompanied this code. If applicable, add the following below the
   35.26 + * License Header, with the fields enclosed by brackets [] replaced by
   35.27 + * your own identifying information:
   35.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   35.29 + *
   35.30 + * If you wish your version of this file to be governed by only the CDDL
   35.31 + * or only the GPL Version 2, indicate your decision by adding
   35.32 + * "[Contributor] elects to include this software in this distribution
   35.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   35.34 + * single choice of license, a recipient has the option to distribute
   35.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   35.36 + * to extend the choice of license to its licensees as provided above.
   35.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   35.38 + * Version 2 license, then the option applies only if the new code is
   35.39 + * made subject to such option by the copyright holder.
   35.40 + *
   35.41 + * Contributor(s):
   35.42 + *
   35.43 + * Portions Copyrighted 2011 Sun Microsystems, Inc.
   35.44 + */
   35.45 +package org.netbeans.modules.masterfs.watcher;
   35.46 +
   35.47 +import org.netbeans.modules.masterfs.providers.Notifier;
   35.48 +import java.io.IOException;
   35.49 +import java.lang.ref.ReferenceQueue;
   35.50 +import java.lang.ref.WeakReference;
   35.51 +import java.util.logging.Level;
   35.52 +import org.openide.filesystems.FileObject;
   35.53 +
   35.54 +/**
   35.55 + *
   35.56 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   35.57 + */
   35.58 +class NotifierKeyRef<KEY> extends WeakReference<FileObject> {
   35.59 +    private final KEY key;
   35.60 +    private final int hash;
   35.61 +    private final Notifier<KEY> outer;
   35.62 +
   35.63 +    public NotifierKeyRef(FileObject fo, KEY key, ReferenceQueue<FileObject> queue, final Notifier<KEY> outer) {
   35.64 +        super(fo, queue);
   35.65 +        this.outer = outer;
   35.66 +        this.key = key;
   35.67 +        this.hash = fo.hashCode();
   35.68 +        if (key != null) {
   35.69 +            Watcher.LOG.log(Level.FINE, "Adding watch for {0}", key);
   35.70 +        }
   35.71 +    }
   35.72 +
   35.73 +    @Override
   35.74 +    public FileObject get() {
   35.75 +        return super.get();
   35.76 +    }
   35.77 +
   35.78 +    @Override
   35.79 +    public boolean equals(Object obj) {
   35.80 +        if (obj == this) {
   35.81 +            return true;
   35.82 +        }
   35.83 +        try {
   35.84 +            NotifierKeyRef kr = (NotifierKeyRef) obj;
   35.85 +            FileObject mine = get();
   35.86 +            FileObject theirs = kr.get();
   35.87 +            if (mine == null) {
   35.88 +                return theirs == null;
   35.89 +            } else {
   35.90 +                return mine.equals(theirs);
   35.91 +            }
   35.92 +        } catch (ClassCastException ex) {
   35.93 +            return false;
   35.94 +        }
   35.95 +    }
   35.96 +
   35.97 +    final void removeWatch() throws IOException {
   35.98 +        Watcher.LOG.log(Level.FINE, "Removing watch for {0}", key);
   35.99 +        NotifierAccessor.getDefault().removeWatch(outer, key);
  35.100 +    }
  35.101 +
  35.102 +    @Override
  35.103 +    public int hashCode() {
  35.104 +        return hash;
  35.105 +    }
  35.106 +    
  35.107 +}
    36.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/OSXNotifier.java	Sat Dec 24 00:28:54 2011 +0000
    36.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.3 @@ -1,290 +0,0 @@
    36.4 -/*
    36.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    36.6 - *
    36.7 - * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    36.8 - *
    36.9 - * The contents of this file are subject to the terms of either the GNU
   36.10 - * General Public License Version 2 only ("GPL") or the Common
   36.11 - * Development and Distribution License("CDDL") (collectively, the
   36.12 - * "License"). You may not use this file except in compliance with the
   36.13 - * License. You can obtain a copy of the License at
   36.14 - * http://www.netbeans.org/cddl-gplv2.html
   36.15 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   36.16 - * specific language governing permissions and limitations under the
   36.17 - * License.  When distributing the software, include this License Header
   36.18 - * Notice in each file and include the License file at
   36.19 - * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   36.20 - * particular file as subject to the "Classpath" exception as provided
   36.21 - * by Sun in the GPL Version 2 section of the License file that
   36.22 - * accompanied this code. If applicable, add the following below the
   36.23 - * License Header, with the fields enclosed by brackets [] replaced by
   36.24 - * your own identifying information:
   36.25 - * "Portions Copyrighted [year] [name of copyright owner]"
   36.26 - *
   36.27 - * If you wish your version of this file to be governed by only the CDDL
   36.28 - * or only the GPL Version 2, indicate your decision by adding
   36.29 - * "[Contributor] elects to include this software in this distribution
   36.30 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
   36.31 - * single choice of license, a recipient has the option to distribute
   36.32 - * your version of this file under either the CDDL, the GPL Version 2 or
   36.33 - * to extend the choice of license to its licensees as provided above.
   36.34 - * However, if you add GPL Version 2 code and therefore, elected the GPL
   36.35 - * Version 2 license, then the option applies only if the new code is
   36.36 - * made subject to such option by the copyright holder.
   36.37 - *
   36.38 - * Contributor(s):
   36.39 - *
   36.40 - * Portions Copyrighted 2010 Sun Microsystems, Inc.
   36.41 - */
   36.42 -
   36.43 -package org.netbeans.modules.masterfs.watcher;
   36.44 -
   36.45 -import com.sun.jna.Callback;
   36.46 -import com.sun.jna.Library;
   36.47 -import com.sun.jna.Native;
   36.48 -import com.sun.jna.NativeLong;
   36.49 -import com.sun.jna.Pointer;
   36.50 -import java.io.IOException;
   36.51 -import java.io.InterruptedIOException;
   36.52 -import java.util.concurrent.BlockingQueue;
   36.53 -import java.util.concurrent.Exchanger;
   36.54 -import java.util.concurrent.ExecutorService;
   36.55 -import java.util.concurrent.Executors;
   36.56 -import java.util.concurrent.LinkedBlockingQueue;
   36.57 -import java.util.concurrent.ThreadFactory;
   36.58 -import java.util.logging.Level;
   36.59 -import java.util.logging.Logger;
   36.60 -
   36.61 -/**
   36.62 - *
   36.63 - * @author Tomas Zezula
   36.64 - */
   36.65 -public class OSXNotifier extends Notifier<Void> {
   36.66 -    private static final Level DEBUG_LOG_LEVEL = Level.FINE;
   36.67 -    private static final Level PERF_LOG_LEVEL = Level.FINE;
   36.68 -    private static final long kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFFL;
   36.69 -    private static final int kFSEventStreamCreateFlagNoDefer = 0x00000002;
   36.70 -    private static final int kFSEventStreamEventFlagMustScanSubDirs = 0x00000001;
   36.71 -    private static final int kFSEventStreamEventFlagMount = 0x00000040;
   36.72 -    private static final int kFSEventStreamEventFlagUnmount = 0x00000080;
   36.73 -    private static final double LATENCY = 1.0f;
   36.74 -    private static final int ENC_MAC_ROMAN = 0;
   36.75 -    private static final String DEFAULT_RUN_LOOP_MODE = "kCFRunLoopDefaultMode";    //NOI18N
   36.76 -    private static final Logger LOG = Logger.getLogger(OSXNotifier.class.getName());
   36.77 -    private final CoreFoundation cf;
   36.78 -    private final CoreServices cs;
   36.79 -    private final EventCallback callback;
   36.80 -    private final BlockingQueue<String> events;
   36.81 -    //@GuardedBy("this")
   36.82 -    private ExecutorService worker;
   36.83 -    //@GuardedBy("this")
   36.84 -    private Pointer[] rtData;
   36.85 -    
   36.86 -    private static final String ALL_CHANGE = "ALL-CHANGE";  //xxx - shouldn't be global in Notifier rather than using null?
   36.87 -
   36.88 -    public OSXNotifier() {
   36.89 -        cf = (CoreFoundation) Native.loadLibrary("CoreFoundation",CoreFoundation.class);    //NOI18N
   36.90 -        cs = (CoreServices) Native.loadLibrary("CoreServices",CoreServices.class);          //NOI18N
   36.91 -        callback = new EventCallbackImpl();
   36.92 -        events = new LinkedBlockingQueue<String>();
   36.93 -    }
   36.94 -
   36.95 -
   36.96 -    public @Override Void addWatch(String path) throws IOException {
   36.97 -        return null;
   36.98 -    }
   36.99 -
  36.100 -    public @Override void removeWatch(Void key) throws IOException {
  36.101 -        // ignore
  36.102 -    }
  36.103 -
  36.104 -    public @Override String nextEvent() throws IOException, InterruptedException {
  36.105 -        final String event = events.take();
  36.106 -        return event == ALL_CHANGE ? null : event;
  36.107 -    }
  36.108 -
  36.109 -    public synchronized void start() throws IOException {
  36.110 -        if (worker != null) {
  36.111 -            throw new IllegalStateException("FileSystemWatcher already started.");  //NOI18N
  36.112 -        }
  36.113 -        worker = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
  36.114 -        final Exchanger<Object> exchanger = new Exchanger<Object>();
  36.115 -        worker.execute(new Runnable() {
  36.116 -            public @Override void run () {
  36.117 -                try {
  36.118 -                    Pointer[] _rtData = null;
  36.119 -                    try {
  36.120 -                        _rtData = createFSEventStream();
  36.121 -                    } catch (Throwable ex) {
  36.122 -                        exchanger.exchange(ex);
  36.123 -                    } finally {
  36.124 -                        if (_rtData != null) {
  36.125 -                            exchanger.exchange(_rtData);
  36.126 -                            cf.CFRunLoopRun();
  36.127 -                        }
  36.128 -                    }
  36.129 -                } catch (InterruptedException ie) {
  36.130 -                    LOG.log(Level.WARNING, "Watcher interruped during start", ie);  //NOI18N
  36.131 -                }
  36.132 -            }
  36.133 -        });
  36.134 -        final Object _data;
  36.135 -        try {
  36.136 -            _data = exchanger.exchange(null);
  36.137 -        } catch (InterruptedException ex) {
  36.138 -            throw (InterruptedIOException)new InterruptedIOException().initCause(ex);
  36.139 -        }
  36.140 -        assert _data != null;
  36.141 -        if (_data instanceof Throwable) {
  36.142 -            worker.shutdown();
  36.143 -            worker = null;
  36.144 -            throw new IOException((Throwable)_data);
  36.145 -        } else {
  36.146 -            rtData = (Pointer[]) _data;
  36.147 -        }
  36.148 -    }
  36.149 -
  36.150 -    @Override
  36.151 -    public synchronized void stop() throws IOException {
  36.152 -        if (worker == null) {
  36.153 -            throw new IllegalStateException("FileSystemWatcher is not started.");  //NOI18N
  36.154 -        }
  36.155 -        assert rtData != null;
  36.156 -        assert rtData.length == 2;
  36.157 -        assert rtData[0] != null;
  36.158 -        assert rtData[1] != null;
  36.159 -        cs.FSEventStreamStop(rtData[0]);
  36.160 -        cs.FSEventStreamInvalidate(rtData[0]);
  36.161 -        cs.FSEventStreamRelease(rtData[0]);
  36.162 -        cf.CFRunLoopStop(rtData[1]);
  36.163 -        worker.shutdown();
  36.164 -        worker = null;
  36.165 -        rtData = null;
  36.166 -    }
  36.167 -
  36.168 -    private Pointer[] createFSEventStream() throws IOException {
  36.169 -        final Pointer root = cf.CFStringCreateWithCString(Pointer.NULL,"/",ENC_MAC_ROMAN);  //NOI18N
  36.170 -        if (root == Pointer.NULL) {
  36.171 -            throw new IOException("Path creation failed.");     //NOI18N
  36.172 -        }
  36.173 -        final Pointer arr = cf.CFArrayCreateMutable(Pointer.NULL, new NativeLong(1), Pointer.NULL);
  36.174 -        if (arr == Pointer.NULL) {
  36.175 -            throw new IOException("Path list creation failed.");    //NOI18N
  36.176 -        }
  36.177 -        cf.CFArrayAppendValue(arr, root);
  36.178 -
  36.179 -        final Pointer eventStream = cs.FSEventStreamCreate(Pointer.NULL, callback, Pointer.NULL, arr, kFSEventStreamEventIdSinceNow, LATENCY, kFSEventStreamCreateFlagNoDefer);
  36.180 -        if (eventStream == Pointer.NULL) {
  36.181 -            throw new IOException("Creation of FSEventStream failed."); //NOI18N
  36.182 -        }
  36.183 -        final Pointer loop = cf.CFRunLoopGetCurrent();
  36.184 -        if (eventStream == Pointer.NULL) {
  36.185 -            throw new IOException("Cannot find run loop for caller.");  //NOI18N
  36.186 -        }
  36.187 -        final Pointer kCFRunLoopDefaultMode = findDefaultMode(loop);
  36.188 -        if (kCFRunLoopDefaultMode == null) {
  36.189 -            throw new IOException("Caller has no defaul run loop mode.");   //NOI18N
  36.190 -        }
  36.191 -        cs.FSEventStreamScheduleWithRunLoop(eventStream, loop, kCFRunLoopDefaultMode);
  36.192 -        if (LOG.isLoggable(DEBUG_LOG_LEVEL)) {
  36.193 -            LOG.log(DEBUG_LOG_LEVEL, getStreamDescription(eventStream));
  36.194 -        }
  36.195 -        cs.FSEventStreamStart(eventStream);
  36.196 -        return new Pointer[] {eventStream, loop};
  36.197 -    }
  36.198 -
  36.199 -    private Pointer findDefaultMode(final Pointer runLoop) {
  36.200 -        final Pointer modes = cf.CFRunLoopCopyAllModes(runLoop);
  36.201 -        if (modes != Pointer.NULL) {
  36.202 -            final int modesCount = cf.CFArrayGetCount(modes).intValue();
  36.203 -            for (int i=0; i< modesCount; i++) {
  36.204 -                final Pointer mode = cf.CFArrayGetValueAtIndex(modes, new NativeLong(i));
  36.205 -                if (mode != Pointer.NULL && DEFAULT_RUN_LOOP_MODE.equals(cf.CFStringGetCStringPtr(mode, ENC_MAC_ROMAN))) {
  36.206 -                    return mode;
  36.207 -                }
  36.208 -            }
  36.209 -        }
  36.210 -        return null;
  36.211 -    }
  36.212 -
  36.213 -    private String getStreamDescription(final Pointer eventStream) {
  36.214 -        final Pointer desc = cs.FSEventStreamCopyDescription(eventStream);
  36.215 -        return desc == Pointer.NULL ? "" : cf.CFStringGetCStringPtr(desc, ENC_MAC_ROMAN);   //NOI18N
  36.216 -    }
  36.217 -
  36.218 -    public static interface EventCallback extends Callback {
  36.219 -        void invoke(Pointer streamRef,
  36.220 -                    Pointer clientCallBackInfo,
  36.221 -                    NativeLong numEvents,
  36.222 -                    Pointer eventPaths,
  36.223 -                    Pointer eventFlags,
  36.224 -                    Pointer eventIds);
  36.225 -    }
  36.226 -
  36.227 -    public static interface CoreFoundation extends Library {
  36.228 -        Pointer CFRunLoopGetCurrent();
  36.229 -        void CFRunLoopRun();
  36.230 -        void CFRunLoopStop(Pointer loop);
  36.231 -        Pointer CFRunLoopCopyAllModes(Pointer loop);
  36.232 -
  36.233 -        Pointer CFArrayCreateMutable(Pointer allocator, NativeLong size, Pointer callback);
  36.234 -        void CFArrayAppendValue(Pointer theArray, Pointer value);
  36.235 -        Pointer CFArrayGetValueAtIndex(Pointer theArray, NativeLong index);
  36.236 -        NativeLong CFArrayGetCount(Pointer theArray);
  36.237 -
  36.238 -        Pointer CFStringCreateWithCString(Pointer allocator, String string, int encoding);
  36.239 -        String CFStringGetCStringPtr(Pointer theString, int encoding);
  36.240 -    }
  36.241 -
  36.242 -    public static interface CoreServices extends Library {
  36.243 -        Pointer FSEventStreamCreate(Pointer allocator, EventCallback callback, Pointer ctx, Pointer pathsToWatch, long sinceWhen, double latency, int flags);
  36.244 -        Pointer FSEventStreamCopyDescription(Pointer stream);
  36.245 -        void FSEventStreamScheduleWithRunLoop(Pointer stream, Pointer loop, Pointer mode);
  36.246 -        void FSEventStreamUnscheduleFromRunLoop(Pointer stream, Pointer loop, Pointer mode);
  36.247 -        void FSEventStreamStart(Pointer stream);
  36.248 -        void FSEventStreamStop(Pointer stream);
  36.249 -        void FSEventStreamInvalidate(Pointer stream);
  36.250 -        void FSEventStreamRelease(Pointer stream);
  36.251 -    }
  36.252 -
  36.253 -    private static class DaemonThreadFactory implements ThreadFactory {
  36.254 -        @Override
  36.255 -        public Thread newThread(Runnable r) {
  36.256 -            final Thread t = new Thread(r);
  36.257 -            t.setDaemon(true);
  36.258 -            return t;
  36.259 -        }
  36.260 -
  36.261 -    }
  36.262 -
  36.263 -    private class EventCallbackImpl implements EventCallback {
  36.264 -        @Override
  36.265 -        public void invoke(Pointer streamRef, Pointer clientCallBackInfo, NativeLong numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds) {
  36.266 -            final long st = System.currentTimeMillis();
  36.267 -            final int length = numEvents.intValue();
  36.268 -            final Pointer[] pointers = eventPaths.getPointerArray(0, length);
  36.269 -            int flags[];
  36.270 -            if (eventFlags == null) {
  36.271 -                flags = new int[length];
  36.272 -                LOG.log(DEBUG_LOG_LEVEL, "FSEventStreamCallback eventFlags == null, expected int[] of size {0}", length); //NOI18N
  36.273 -            } else {
  36.274 -                flags = eventFlags.getIntArray(0, length);
  36.275 -            }
  36.276 -            for (int i=0; i<length; i++) {
  36.277 -                final Pointer p = pointers[i];
  36.278 -                int flag = flags[i];
  36.279 -                final String path = p.getString(0);
  36.280 -
  36.281 -                if ((flag & kFSEventStreamEventFlagMustScanSubDirs) ==  kFSEventStreamEventFlagMustScanSubDirs ||
  36.282 -                    (flag & kFSEventStreamEventFlagMount) == kFSEventStreamEventFlagMount ||
  36.283 -                    (flag & kFSEventStreamEventFlagUnmount) == kFSEventStreamEventFlagUnmount) {
  36.284 -                    events.add(ALL_CHANGE);
  36.285 -                } else {
  36.286 -                    events.add(path);
  36.287 -                }
  36.288 -                LOG.log(DEBUG_LOG_LEVEL, "Event on {0}", new Object[]{path});
  36.289 -            }
  36.290 -            LOG.log(PERF_LOG_LEVEL, "Callback time: {0}", (System.currentTimeMillis() - st));
  36.291 -        }
  36.292 -    }
  36.293 -}
    37.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java	Sat Dec 24 00:28:54 2011 +0000
    37.2 +++ b/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java	Tue Dec 27 19:31:42 2011 +0100
    37.3 @@ -39,11 +39,11 @@
    37.4  
    37.5  package org.netbeans.modules.masterfs.watcher;
    37.6  
    37.7 +import org.netbeans.modules.masterfs.providers.Notifier;
    37.8  import java.awt.Image;
    37.9  import java.io.File;
   37.10  import java.io.IOException;
   37.11  import java.lang.ref.ReferenceQueue;
   37.12 -import java.util.HashMap;
   37.13  import java.util.HashSet;
   37.14  import java.util.List;
   37.15  import java.util.Map;
   37.16 @@ -57,10 +57,9 @@
   37.17  import org.netbeans.modules.masterfs.providers.ProvidedExtensions;
   37.18  import org.openide.filesystems.FileObject;
   37.19  import org.netbeans.modules.masterfs.providers.AnnotationProvider;
   37.20 -import org.netbeans.modules.masterfs.watcher.Notifier.KeyRef;
   37.21  import org.openide.util.Lookup;
   37.22 +import org.openide.util.Lookup.Item;
   37.23  import org.openide.util.RequestProcessor;
   37.24 -import org.openide.util.Utilities;
   37.25  import org.openide.util.lookup.ServiceProvider;
   37.26  import org.openide.util.lookup.ServiceProviders;
   37.27  
   37.28 @@ -75,8 +74,7 @@
   37.29  public final class Watcher extends AnnotationProvider {
   37.30      static final Logger LOG = Logger.getLogger(Watcher.class.getName());
   37.31      private static final Map<FileObject,int[]> MODIFIED = new WeakHashMap<FileObject, int[]>();
   37.32 -    
   37.33 -    private Ext<?> ext;
   37.34 +    private final Ext<?> ext;
   37.35      
   37.36      public Watcher() {
   37.37          // Watcher disabled manually or for some tests
   37.38 @@ -84,14 +82,9 @@
   37.39              ext = null;
   37.40              return;
   37.41          }
   37.42 -        
   37.43          ext = make(getNotifierForPlatform());
   37.44      }
   37.45      
   37.46 -    final void installNotifier(Notifier<?> n) {
   37.47 -        ext = make(n);
   37.48 -    }
   37.49 -    
   37.50      private static Ext<?> ext() {
   37.51          final Watcher w = Lookup.getDefault().lookup(Watcher.class);
   37.52          return w == null ? null : w.ext;
   37.53 @@ -182,7 +175,7 @@
   37.54          private final ReferenceQueue<FileObject> REF = new ReferenceQueue<FileObject>();
   37.55          private final Notifier<KEY> impl;
   37.56          private final Object LOCK = new Object();
   37.57 -        private final Set<KeyRef> references = new HashSet<KeyRef>();
   37.58 +        private final Set<NotifierKeyRef> references = new HashSet<NotifierKeyRef>();
   37.59          private final Thread watcher;
   37.60          private volatile boolean shutdown;
   37.61  
   37.62 @@ -221,7 +214,7 @@
   37.63                  LOG.log(Level.INFO, "Exception while clearing the queue", ex);
   37.64              }
   37.65              synchronized (LOCK) {
   37.66 -                KeyRef kr = impl.new KeyRef(fo, null, null);
   37.67 +                NotifierKeyRef<KEY> kr = new NotifierKeyRef<KEY>(fo, null, null, impl);
   37.68                  return getReferences().contains(kr);
   37.69              }
   37.70          }
   37.71 @@ -236,13 +229,13 @@
   37.72                  LOG.log(Level.INFO, "Exception while clearing the queue", ex);
   37.73              }
   37.74              synchronized (LOCK) {
   37.75 -                KeyRef kr = impl.new KeyRef(fo, null, null);
   37.76 +                NotifierKeyRef<KEY> kr = new NotifierKeyRef<KEY>(fo, null, null, impl);
   37.77                  if (getReferences().contains(kr)) {
   37.78                      return;
   37.79                  }
   37.80  
   37.81                  try {
   37.82 -                    getReferences().add(impl.new KeyRef(fo, impl.addWatch(fo.getPath()), REF));
   37.83 +                    getReferences().add(new NotifierKeyRef<KEY>(fo, NotifierAccessor.getDefault().addWatch(impl, fo.getPath()), REF, impl));
   37.84                  } catch (IOException ex) {
   37.85                      // XXX: handle resource overflow gracefully
   37.86                      LOG.log(Level.WARNING, "Cannot add filesystem watch for {0}: {1}", new Object[] {fo.getPath(), ex});
   37.87 @@ -254,12 +247,12 @@
   37.88          final void unregister(FileObject fo) {
   37.89              assert fo.isFolder() : "Should be a folder: " + fo;
   37.90              synchronized (LOCK) {
   37.91 -                final KeyRef[] equalOne = new KeyRef[1];
   37.92 -                KeyRef kr = impl.new KeyRef(fo, null, null) {
   37.93 +                final NotifierKeyRef[] equalOne = new NotifierKeyRef[1];
   37.94 +                NotifierKeyRef<KEY> kr = new NotifierKeyRef<KEY>(fo, null, null, impl) {
   37.95                      @Override
   37.96                      public boolean equals(Object obj) {
   37.97                          if (super.equals(obj)) {
   37.98 -                            equalOne[0] = (KeyRef)obj;
   37.99 +                            equalOne[0] = (NotifierKeyRef)obj;
  37.100                              return true;
  37.101                          } else {
  37.102                              return false;
  37.103 @@ -287,7 +280,7 @@
  37.104          
  37.105          final void clearQueue() throws IOException {
  37.106              for (;;) {
  37.107 -                KeyRef kr = (KeyRef)REF.poll();
  37.108 +                NotifierKeyRef kr = (NotifierKeyRef)REF.poll();
  37.109                  if (kr == null) {
  37.110                      break;
  37.111                  }
  37.112 @@ -302,12 +295,12 @@
  37.113              while (!shutdown) {
  37.114                  try {
  37.115                      clearQueue();
  37.116 -                    String path = impl.nextEvent();
  37.117 +                    String path = NotifierAccessor.getDefault().nextEvent(impl);
  37.118                      LOG.log(Level.FINEST, "nextEvent: {0}", path); 
  37.119                      if (path == null) { // all dirty
  37.120                          Set<FileObject> set = new HashSet<FileObject>();
  37.121                          synchronized (LOCK) {
  37.122 -                            for (KeyRef kr : getReferences()) {
  37.123 +                            for (NotifierKeyRef kr : getReferences()) {
  37.124                                  final FileObject ref = kr.get();
  37.125                                  if (ref != null) {
  37.126                                      set.add(ref);
  37.127 @@ -325,7 +318,7 @@
  37.128                          }
  37.129                          if (fo != null) {
  37.130                              synchronized (LOCK) {
  37.131 -                                KeyRef kr = impl.new KeyRef(fo, null, null);
  37.132 +                                NotifierKeyRef<KEY> kr = new NotifierKeyRef<KEY>(fo, null, null, impl);
  37.133                                  if (getReferences().contains(kr)) {
  37.134                                      enqueue(fo);
  37.135                                  }
  37.136 @@ -347,11 +340,11 @@
  37.137          final void shutdown() throws IOException, InterruptedException {
  37.138              shutdown = true;
  37.139              watcher.interrupt();
  37.140 -            impl.stop();
  37.141 +            NotifierAccessor.getDefault().stop(impl);
  37.142              watcher.join(1000);
  37.143          }
  37.144  
  37.145 -        private Set<KeyRef> getReferences() {
  37.146 +        private Set<NotifierKeyRef> getReferences() {
  37.147              assert Thread.holdsLock(LOCK);
  37.148              return references;
  37.149          }
  37.150 @@ -416,35 +409,19 @@
  37.151       * @return a suitable {@link Notifier} implementation or <code>null</code>.
  37.152       */
  37.153      private static Notifier<?> getNotifierForPlatform() {
  37.154 -        try {
  37.155 -            if (Utilities.isWindows()) {
  37.156 -                return new WindowsNotifier();
  37.157 +        for (Item<Notifier> item : Lookup.getDefault().lookupResult(Notifier.class).allItems()) {
  37.158 +            try {
  37.159 +                final Notifier notifier = item.getInstance();
  37.160 +                NotifierAccessor.getDefault().start(notifier);
  37.161 +                return notifier;
  37.162 +            } catch (IOException ex) {
  37.163 +                LOG.log(Level.INFO, "Notifier {0} refused to be initialized", item.getType()); // NOI18N
  37.164 +                LOG.log(Level.FINE, null, ex);
  37.165 +            } catch (Exception ex) {
  37.166 +                LOG.log(Level.INFO, "Exception while instantiating " + item, ex);
  37.167 +            } catch (LinkageError ex) {
  37.168 +                LOG.log(Level.INFO, "Linkage error for " + item, ex);
  37.169              }
  37.170 -            if (Utilities.getOperatingSystem() == Utilities.OS_LINUX) {
  37.171 -                return new LinuxNotifier();
  37.172 -            }
  37.173 -            if (Utilities.getOperatingSystem() == Utilities.OS_MAC) {
  37.174 -                try {
  37.175 -                    final OSXNotifier notifier = new OSXNotifier();
  37.176 -                    notifier.start();
  37.177 -                    return notifier;
  37.178 -                } catch (IOException ioe) {
  37.179 -                    LOG.log(Level.INFO, null, ioe);
  37.180 -                }
  37.181 -            }
  37.182 -            if (Utilities.getOperatingSystem() == Utilities.OS_SOLARIS &&
  37.183 -                Boolean.getBoolean("org.netbeans.modules.masterfs.watcher.FAM")) {
  37.184 -                // disabled by default, see IZ 206670
  37.185 -                try {
  37.186 -                    return new FAMNotifier();
  37.187 -                } catch (Exception e) {
  37.188 -                    LOG.log(Level.INFO, null, e);
  37.189 -                } catch (LinkageError x) {
  37.190 -                    //this is normal not to have fam in the system, do not report
  37.191 -                }
  37.192 -            }
  37.193 -        } catch (LinkageError x) {
  37.194 -            LOG.warning(x.toString());
  37.195          }
  37.196          return null;
  37.197      }
    38.1 --- a/masterfs/src/org/netbeans/modules/masterfs/watcher/WindowsNotifier.java	Sat Dec 24 00:28:54 2011 +0000
    38.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.3 @@ -1,413 +0,0 @@
    38.4 -/*
    38.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    38.6 - *
    38.7 - * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
    38.8 - *
    38.9 - * The contents of this file are subject to the terms of either the GNU
   38.10 - * General Public License Version 2 only ("GPL") or the Common
   38.11 - * Development and Distribution License("CDDL") (collectively, the
   38.12 - * "License"). You may not use this file except in compliance with the
   38.13 - * License. You can obtain a copy of the License at
   38.14 - * http://www.netbeans.org/cddl-gplv2.html
   38.15 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   38.16 - * specific language governing permissions and limitations under the
   38.17 - * License.  When distributing the software, include this License Header
   38.18 - * Notice in each file and include the License file at
   38.19 - * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
   38.20 - * particular file as subject to the "Classpath" exception as provided
   38.21 - * by Sun in the GPL Version 2 section of the License file that
   38.22 - * accompanied this code. If applicable, add the following below the
   38.23 - * License Header, with the fields enclosed by brackets [] replaced by
   38.24 - * your own identifying information:
   38.25 - * "Portions Copyrighted [year] [name of copyright owner]"
   38.26 - *
   38.27 - * If you wish your version of this file to be governed by only the CDDL
   38.28 - * or only the GPL Version 2, indicate your decision by adding
   38.29 - * "[Contributor] elects to include this software in this distribution
   38.30 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
   38.31 - * single choice of license, a recipient has the option to distribute
   38.32 - * your version of this file under either the CDDL, the GPL Version 2 or
   38.33 - * to extend the choice of license to its licensees as provided above.
   38.34 - * However, if you add GPL Version 2 code and therefore, elected the GPL
   38.35 - * Version 2 license, then the option applies only if the new code is
   38.36 - * made subject to such option by the copyright holder.
   38.37 - *
   38.38 - * Contributor(s):
   38.39 - *
   38.40 - * Portions Copyrighted 2010 Sun Microsystems, Inc.
   38.41 - */
   38.42 -
   38.43 -package org.netbeans.modules.masterfs.watcher;
   38.44 -
   38.45 -import com.sun.jna.FromNativeContext;
   38.46 -import com.sun.jna.IntegerType;
   38.47 -import com.sun.jna.Library;
   38.48 -import com.sun.jna.Native;
   38.49 -import com.sun.jna.Pointer;
   38.50 -import com.sun.jna.PointerType;
   38.51 -import com.sun.jna.Structure;
   38.52 -import com.sun.jna.ptr.ByReference;
   38.53 -import java.io.File;
   38.54 -import java.io.IOException;
   38.55 -import java.util.HashMap;
   38.56 -import java.util.Map;
   38.57 -
   38.58 -import com.sun.jna.ptr.IntByReference;
   38.59 -import com.sun.jna.ptr.PointerByReference;
   38.60 -import com.sun.jna.win32.StdCallLibrary;
   38.61 -import com.sun.jna.win32.W32APIFunctionMapper;
   38.62 -import com.sun.jna.win32.W32APITypeMapper;
   38.63 -import java.io.InterruptedIOException;
   38.64 -import java.util.concurrent.BlockingQueue;
   38.65 -import java.util.concurrent.LinkedBlockingQueue;
   38.66 -import java.util.logging.Level;
   38.67 -
   38.68 -/**
   38.69 - * A {@link Notifier} implementation using Win32 API ReadDirectoryChangesW.
   38.70 - * Based on JNA examples and platform library stubs.
   38.71 - *
   38.72 - * @author nenik
   38.73 - */
   38.74 -public class WindowsNotifier extends Notifier<Void> {
   38.75 -
   38.76 -    public static final class HANDLE extends PointerType {
   38.77 -        private boolean immutable;
   38.78 -        public HANDLE() { }
   38.79 -        public HANDLE(Pointer p) {
   38.80 -            setPointer(p);
   38.81 -            immutable = true;
   38.82 -        }
   38.83 -
   38.84 -        /** Override to the appropriate object for INVALID_HANDLE_VALUE. */
   38.85 -        @Override
   38.86 -        public Object fromNative(Object nativeValue, FromNativeContext context) {
   38.87 -            Object o = super.fromNative(nativeValue, context);
   38.88 -            if (INVALID_HANDLE_VALUE.equals(o))
   38.89 -                return INVALID_HANDLE_VALUE;
   38.90 -            return o;
   38.91 -        }
   38.92 -
   38.93 -        @Override
   38.94 -        public void setPointer(Pointer p) {
   38.95 -            if (immutable) {
   38.96 -                throw new UnsupportedOperationException("immutable reference");
   38.97 -            }
   38.98 -
   38.99 -            super.setPointer(p);
  38.100 -        }
  38.101 -    }
  38.102 -
  38.103 -    public static class ULONG_PTR extends IntegerType {
  38.104 -        public ULONG_PTR() {
  38.105 -                this(0);
  38.106 -        }
  38.107 -
  38.108 -        public ULONG_PTR(long value) {
  38.109 -                super(Pointer.SIZE, value);
  38.110 -        }
  38.111 -    }
  38.112 -
  38.113 -    public static class OVERLAPPED extends Structure {
  38.114 -        public ULONG_PTR Internal;
  38.115 -        public ULONG_PTR InternalHigh;
  38.116 -        public int Offset;
  38.117 -        public int OffsetHigh;
  38.118 -        public HANDLE hEvent;
  38.119 -    }
  38.120 -
  38.121 -    public static HANDLE INVALID_HANDLE_VALUE = new HANDLE(Pointer.createConstant(
  38.122 -    		Pointer.SIZE == 8 ? -1 : 0xFFFFFFFFL));
  38.123 -
  38.124 -
  38.125 -
  38.126 -    public static class HANDLEByReference extends ByReference {
  38.127 -
  38.128 -    	public HANDLEByReference() {
  38.129 -            this(null);
  38.130 -        }
  38.131 -
  38.132 -        public HANDLEByReference(HANDLE h) {
  38.133 -            super(Pointer.SIZE);
  38.134 -            setValue(h);
  38.135 -        }
  38.136 -
  38.137 -        public void setValue(HANDLE h) {
  38.138 -            getPointer().setPointer(0, h != null ? h.getPointer() : null);
  38.139 -        }
  38.140 -
  38.141 -        public HANDLE getValue() {
  38.142 -            Pointer p = getPointer().getPointer(0);
  38.143 -            if (p == null)
  38.144 -                return null;
  38.145 -            if (INVALID_HANDLE_VALUE.getPointer().equals(p))
  38.146 -                return INVALID_HANDLE_VALUE;
  38.147 -            HANDLE h = new HANDLE();
  38.148 -            h.setPointer(p);
  38.149 -            return h;
  38.150 -        }
  38.151 -    }
  38.152 -
  38.153 -    public static class FILE_NOTIFY_INFORMATION extends Structure {
  38.154 -        public int NextEntryOffset;
  38.155 -        public int Action;
  38.156 -        public int FileNameLength;
  38.157 -        // filename is not nul-terminated, so we can't use a String/WString
  38.158 -        public char[] FileName = new char[1];
  38.159 -
  38.160 -        private FILE_NOTIFY_INFORMATION() {}
  38.161 -
  38.162 -        public FILE_NOTIFY_INFORMATION(int size) {
  38.163 -            if (size < size()) {
  38.164 -               throw new IllegalArgumentException("Size must greater than "
  38.165 -                               + size() + ", requested " + size);
  38.166 -            }
  38.167 -            allocateMemory(size);
  38.168 -        }
  38.169 -
  38.170 -        /** WARNING: this filename may be either the short or long form of the filename. */
  38.171 -        public String getFilename() {
  38.172 -            return new String(FileName, 0, FileNameLength/2);
  38.173 -        }
  38.174 -
  38.175 -        @Override
  38.176 -        public void read() {
  38.177 -            // avoid reading filename until we know how long it is
  38.178 -            FileName = new char[0];
  38.179 -            super.read();
  38.180 -            FileName = getPointer().getCharArray(12, FileNameLength/2);
  38.181 -        }
  38.182 -
  38.183 -        public FILE_NOTIFY_INFORMATION next() {
  38.184 -            if (NextEntryOffset == 0)
  38.185 -                    return null;
  38.186 -            FILE_NOTIFY_INFORMATION next = new FILE_NOTIFY_INFORMATION();
  38.187 -            next.useMemory(getPointer(), NextEntryOffset);
  38.188 -            next.read();
  38.189 -            return next;
  38.190 -        }
  38.191 -    }
  38.192 -
  38.193 -    public static class SECURITY_ATTRIBUTES extends Structure {
  38.194 -        public final int nLength = size();
  38.195 -        public Pointer lpSecurityDescriptor;
  38.196 -        public boolean bInheritHandle;
  38.197 -    }
  38.198 -
  38.199 -    interface Kernel32 extends StdCallLibrary {
  38.200 -        HANDLE CreateFile(String lpFileName, int dwDesiredAccess, int dwShareMode,
  38.201 -                    SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition,
  38.202 -                    int dwFlagsAndAttributes, HANDLE hTemplateFile);
  38.203 -
  38.204 -        HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
  38.205 -                    Pointer CompletionKey, int NumberOfConcurrentThreads);
  38.206 -
  38.207 -        int GetLastError();
  38.208 -
  38.209 -        boolean GetQueuedCompletionStatus(HANDLE CompletionPort,
  38.210 -                    IntByReference lpNumberOfBytes, ByReference lpCompletionKey,
  38.211 -                    PointerByReference lpOverlapped, int dwMilliseconds);
  38.212 -       
  38.213 -        boolean PostQueuedCompletionStatus(HANDLE CompletionPort,
  38.214 -                    int dwNumberOfBytesTransferred, Pointer dwCompletionKey,
  38.215 -                    OVERLAPPED lpOverlapped);
  38.216 -       
  38.217 -        boolean CloseHandle(HANDLE hObject);
  38.218 -
  38.219 -        interface OVERLAPPED_COMPLETION_ROUTINE extends StdCallCallback {
  38.220 -            void callback(int errorCode, int nBytesTransferred,
  38.221 -                            OVERLAPPED overlapped);
  38.222 -        }
  38.223 -
  38.224 -      
  38.225 -        public boolean ReadDirectoryChangesW(HANDLE directory,
  38.226 -                    FILE_NOTIFY_INFORMATION info, int length, boolean watchSubtree,
  38.227 -                int notifyFilter, IntByReference bytesReturned, OVERLAPPED overlapped,
  38.228 -                OVERLAPPED_COMPLETION_ROUTINE completionRoutine);
  38.229 -
  38.230 -
  38.231 -    }
  38.232 -
  38.233 -    final static Kernel32 KERNEL32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class,
  38.234 -            new HashMap() {{
  38.235 -                put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
  38.236 -                put(Library.OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
  38.237 -            }});
  38.238 -
  38.239 -
  38.240 -
  38.241 -    public WindowsNotifier() { // prepare port, start thread?
  38.242 -    }
  38.243 -
  38.244 -    public @Override void removeWatch(Void key) throws IOException {}
  38.245 -
  38.246 -
  38.247 -    public @Override String nextEvent() throws IOException, InterruptedException {
  38.248 -        return events.take();
  38.249 -    }
  38.250 -
  38.251 -    public static final int INFINITE = 0xFFFFFFFF;
  38.252 -
  38.253 -    public static final int FILE_NOTIFY_CHANGE_NAME = 0x00000003;
  38.254 -    public static final int FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
  38.255 -    public static final int FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
  38.256 -    public static final int FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
  38.257 -    public static final int FILE_NOTIFY_CHANGE_CREATION = 0x00000040;
  38.258 -    public static final int FILE_NOTIFY_CHANGE_SECURITY = 0x00000100;
  38.259 -
  38.260 -    private static final int NOTIFY_MASK =
  38.261 -            FILE_NOTIFY_CHANGE_NAME |
  38.262 -            FILE_NOTIFY_CHANGE_ATTRIBUTES |
  38.263 -            FILE_NOTIFY_CHANGE_SIZE |
  38.264 -            FILE_NOTIFY_CHANGE_LAST_WRITE |
  38.265 -            FILE_NOTIFY_CHANGE_CREATION |
  38.266 -            FILE_NOTIFY_CHANGE_SECURITY;
  38.267 -
  38.268 -    public static final int FILE_LIST_DIRECTORY = 0x00000001;
  38.269 -    public static final int OPEN_EXISTING =      3;
  38.270 -
  38.271 -    public static final int FILE_SHARE_READ  = 0x00000001;
  38.272 -    public static final int FILE_SHARE_WRITE = 0x00000002;
  38.273 -    public static final int FILE_SHARE_DELETE = 0x00000004;
  38.274 -
  38.275 -    public static final int FILE_FLAG_OVERLAPPED =           0x40000000;
  38.276 -    public static final int FILE_FLAG_BACKUP_SEMANTICS =     0x02000000;
  38.277 -
  38.278 -    private class FileInfo {
  38.279 -        public final String path;
  38.280 -        public final HANDLE handle;
  38.281 -        public final FILE_NOTIFY_INFORMATION info = new FILE_NOTIFY_INFORMATION(BUFFER_SIZE);
  38.282 -        public final IntByReference infoLength = new IntByReference();
  38.283 -        public final OVERLAPPED overlapped = new OVERLAPPED();
  38.284 -        public FileInfo(String path, HANDLE h) {
  38.285 -            this.path = path;
  38.286 -            this.handle = h;
  38.287 -        }
  38.288 -    }
  38.289 -
  38.290 -    private static int watcherThreadID;
  38.291 -    private Thread watcher;
  38.292 -    private HANDLE port;
  38.293 -    private final Map<String, FileInfo> rootMap = new HashMap<String, FileInfo>();
  38.294 -    private final Map<HANDLE, FileInfo> handleMap = new HashMap<HANDLE, FileInfo>();
  38.295 -    private final BlockingQueue<String> events = new LinkedBlockingQueue<String>();
  38.296 -
  38.297 -    public @Override Void addWatch(String path) throws IOException {
  38.298 -
  38.299 -        if (path.length() < 3 ) throw new IOException("wrong path: " + path);
  38.300 -
  38.301 -        String root = path.substring(0, 3).replace('/', '\\');
  38.302 -        if (root.charAt(1) != ':' || root.charAt(2) != '\\') {
  38.303 -            throw new IOException("wrong path: " + path);
  38.304 -        }
  38.305 -
  38.306 -        if (rootMap.containsKey(root)) return null; // already listening
  38.307 -        path = root; // listen once on the rootpath instead
  38.308 -
  38.309 -        int mask = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
  38.310 -        int flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
  38.311 -        HANDLE handle = KERNEL32.CreateFile(path,
  38.312 -        		FILE_LIST_DIRECTORY,
  38.313 -        		mask, null, OPEN_EXISTING,
  38.314 -                flags, null);
  38.315 -        if (INVALID_HANDLE_VALUE.equals(handle)) {
  38.316 -            throw new IOException("Unable to open " + path + ": "
  38.317 -                                  + KERNEL32.GetLastError());
  38.318 -        }
  38.319 -        FileInfo finfo = new FileInfo(path, handle);
  38.320 -        rootMap.put(path, finfo);
  38.321 -        handleMap.put(handle, finfo);
  38.322 -
  38.323 -        // Existing port is returned
  38.324 -        port = KERNEL32.CreateIoCompletionPort(handle, port, handle.getPointer(), 0);
  38.325 -        if (INVALID_HANDLE_VALUE.equals(port)) {
  38.326 -            throw new IOException("Unable to create/use I/O Completion port "
  38.327 -                    + "for " + path + ": " + KERNEL32.GetLastError());
  38.328 -        }
  38.329 -
  38.330 -        if (!KERNEL32.ReadDirectoryChangesW(handle, finfo.info, finfo.info.size(),
  38.331 -                                        true, NOTIFY_MASK, finfo.infoLength,
  38.332 -                                        finfo.overlapped, null)) {
  38.333 -            int err = KERNEL32.GetLastError();
  38.334 -            throw new IOException("ReadDirectoryChangesW failed on "
  38.335 -                                  + finfo.path + ", handle " + handle
  38.336 -                                  + ": " + err);
  38.337 -        }
  38.338 -        if (watcher == null) {
  38.339 -            Thread t = new Thread("W32 File Monitor") {
  38.340 -                @Override
  38.341 -                public void run() {
  38.342 -                    FileInfo finfo;
  38.343 -                    while (watcher != null) {
  38.344 -                        finfo = waitForChange();
  38.345 -                        if (finfo == null) continue;
  38.346 -
  38.347 -                        try {
  38.348 -                            handleChanges(finfo);
  38.349 -                        } catch(IOException e) {
  38.350 -                            Watcher.LOG.log(Level.INFO, "handleChanges", e); 
  38.351 -                        }
  38.352 -                    }
  38.353 -                }
  38.354 -            };
  38.355 -            t.setDaemon(true);
  38.356 -            t.start();
  38.357 -            watcher = t;
  38.358 -        }
  38.359 -
  38.360 -        return null;
  38.361 -    }
  38.362 -    
  38.363 -    @Override
  38.364 -    public void stop() throws IOException {
  38.365 -        try {
  38.366 -            Thread w = watcher;
  38.367 -            if (w == null) {
  38.368 -                return;
  38.369 -            }
  38.370 -            watcher = null;
  38.371 -            w.interrupt();
  38.372 -            w.join(2000);
  38.373 -        } catch (InterruptedException ex) {
  38.374 -            throw (IOException)new InterruptedIOException().initCause(ex);
  38.375 -        }
  38.376 -    }
  38.377 -
  38.378 -    private void notify(File file) {
  38.379 -        events.add(file.getPath());
  38.380 -    }
  38.381 -
  38.382 -
  38.383 -    private static final int BUFFER_SIZE = 4096;
  38.384 -        
  38.385 -    private void handleChanges(FileInfo finfo) throws IOException {
  38.386 -        FILE_NOTIFY_INFORMATION fni = finfo.info;
  38.387 -        // Lazily fetch the data from native to java - asynchronous update
  38.388 -        fni.read();
  38.389 -        do {
  38.390 -            File file = new File(finfo.path, fni.getFilename());
  38.391 -            notify(file);
  38.392 -            
  38.393 -            fni = fni.next();
  38.394 -        } while (fni != null);
  38.395 -        
  38.396 -        if (!KERNEL32.ReadDirectoryChangesW(finfo.handle, finfo.info,
  38.397 -        		finfo.info.size(), true, NOTIFY_MASK,
  38.398 -        		finfo.infoLength, finfo.overlapped, null)) {        	
  38.399 -        		int err = KERNEL32.GetLastError();
  38.400 -                throw new IOException("ReadDirectoryChangesW failed on "
  38.401 -                                  + finfo.path + ": " + err);
  38.402 -        }
  38.403 -    }
  38.404 -
  38.405 -    private FileInfo waitForChange() {
  38.406 -        IntByReference rcount = new IntByReference();
  38.407 -        HANDLEByReference rkey = new HANDLEByReference();
  38.408 -        PointerByReference roverlap = new PointerByReference();
  38.409 -        KERNEL32.GetQueuedCompletionStatus(port, rcount, rkey, roverlap, INFINITE);
  38.410 -        
  38.411 -        synchronized (this) { 
  38.412 -            return (FileInfo)handleMap.get(rkey.getValue());
  38.413 -        }
  38.414 -    }
  38.415 -
  38.416 -}
    39.1 --- a/masterfs/test/unit/src/org/netbeans/modules/masterfs/watcher/WatcherTest.java	Sat Dec 24 00:28:54 2011 +0000
    39.2 +++ b/masterfs/test/unit/src/org/netbeans/modules/masterfs/watcher/WatcherTest.java	Tue Dec 27 19:31:42 2011 +0100
    39.3 @@ -41,11 +41,13 @@
    39.4   */
    39.5  package org.netbeans.modules.masterfs.watcher;
    39.6  
    39.7 +import org.netbeans.modules.masterfs.providers.Notifier;
    39.8  import java.io.IOException;
    39.9  import java.lang.ref.Reference;
   39.10  import java.lang.ref.WeakReference;
   39.11  import java.util.LinkedList;
   39.12  import java.util.List;
   39.13 +import org.netbeans.junit.MockServices;
   39.14  import org.netbeans.junit.NbTestCase;
   39.15  import org.openide.filesystems.FileChangeAdapter;
   39.16  import org.openide.filesystems.FileObject;
   39.17 @@ -68,10 +70,11 @@
   39.18      @Override
   39.19      protected void setUp() throws Exception {
   39.20          clearWorkDir();
   39.21 +        MockServices.setServices(TestNotifier.class);
   39.22          listener = new L();
   39.23          watcher = Lookup.getDefault().lookup(Watcher.class);
   39.24 -        notify = new TestNotifier();
   39.25 -        watcher.installNotifier(notify);
   39.26 +        notify = Lookup.getDefault().lookup(TestNotifier.class);
   39.27 +        notify.start();
   39.28      }
   39.29  
   39.30      @Override
   39.31 @@ -158,7 +161,7 @@
   39.32      private static final class L extends FileChangeAdapter {
   39.33      }
   39.34      
   39.35 -    private static final class TestNotifier extends Notifier<Integer> {
   39.36 +    public static final class TestNotifier extends Notifier<Integer> {
   39.37          final List<String> registered = new LinkedList<String>();
   39.38  
   39.39          @Override
   39.40 @@ -189,6 +192,10 @@
   39.41                  assertEquals(i + ". " + msg, expected[i], registered.get(i));
   39.42              }
   39.43          }
   39.44 -        
   39.45 +
   39.46 +        @Override
   39.47 +        protected void start() throws IOException {
   39.48 +            registered.clear();
   39.49 +        }
   39.50      }
   39.51  }
    40.1 --- a/nbbuild/cluster.properties	Sat Dec 24 00:28:54 2011 +0000
    40.2 +++ b/nbbuild/cluster.properties	Tue Dec 27 19:31:42 2011 +0100
    40.3 @@ -176,6 +176,10 @@
    40.4          libs.jsr223,\
    40.5          libs.osgi,\
    40.6          masterfs,\
    40.7 +        masterfs.linux,\
    40.8 +        masterfs.macosx,\
    40.9 +        masterfs.solaris,\
   40.10 +        masterfs.windows,\
   40.11          netbinox,\
   40.12          o.jdesktop.layout,\
   40.13          o.n.bootstrap,\
    41.1 --- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java	Sat Dec 24 00:28:54 2011 +0000
    41.2 +++ b/openide.filesystems/src/org/openide/filesystems/FileUtil.java	Tue Dec 27 19:31:42 2011 +0100
    41.3 @@ -1003,7 +1003,13 @@
    41.4       * For example, to make this method work in unit tests in an Ant-based module project,
    41.5       * right-click Unit Test Libraries, Add Unit Test Dependency, check Show Non-API Modules, select Master Filesystem.
    41.6       * (Also right-click the new Master Filesystem node, Edit, uncheck Include in Compile Classpath.)
    41.7 +     * To ensure masterfs (or some other module that can handle the conversion)
    41.8 +     * is present put following line into your module manifest:
    41.9       * </p>
   41.10 +     * <pre>
   41.11 +     * OpenIDE-Module-Needs: org.openide.filesystems.FileUtil.toFileObject
   41.12 +     * </pre>
   41.13 +     * 
   41.14       * @param file a disk file (may or may not exist). This file
   41.15       * must be {@linkplain #normalizeFile normalized}.
   41.16       * @return a corresponding file object, or null if the file does not exist
    42.1 --- a/openide.filesystems/src/org/openide/filesystems/StreamPool.java	Sat Dec 24 00:28:54 2011 +0000
    42.2 +++ b/openide.filesystems/src/org/openide/filesystems/StreamPool.java	Tue Dec 27 19:31:42 2011 +0100
    42.3 @@ -453,9 +453,9 @@
    42.4          }
    42.5          String annotateProp = System.getProperty("org.openide.filesystems.annotateUnclosedStreams"); // NOI18N;
    42.6          annotateUnclosedStreams = Boolean.parseBoolean(annotateProp);
    42.7 -        try {
    42.8 -            assert false;
    42.9 -        } catch (AssertionError ex) {
   42.10 +        boolean assertsOn = false;
   42.11 +        assert assertsOn = true;
   42.12 +        if (assertsOn) {
   42.13              annotateUnclosedStreams = annotateProp == null ? true : ! Boolean.FALSE.toString().equals(annotateProp);
   42.14          }
   42.15          return annotateUnclosedStreams;