Merge branch 'master' of https://code.google.com/p/plexi.fs into OverviewImages
diff --git a/src/com/google/enterprise/adaptor/fs/AclBuilder.java b/src/com/google/enterprise/adaptor/fs/AclBuilder.java
index f2d7952..e8db799 100644
--- a/src/com/google/enterprise/adaptor/fs/AclBuilder.java
+++ b/src/com/google/enterprise/adaptor/fs/AclBuilder.java
@@ -17,8 +17,6 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.enterprise.adaptor.Acl;
-import com.google.enterprise.adaptor.Acl.InheritanceType;
-import com.google.enterprise.adaptor.DocId;
 import com.google.enterprise.adaptor.GroupPrincipal;
 import com.google.enterprise.adaptor.Principal;
 import com.google.enterprise.adaptor.UserPrincipal;
@@ -64,48 +62,30 @@
     this.namespace = namespace;
   }
 
-  public Acl getAcl(DocId inheritId, boolean isDirectory,
-      String fragmentName) throws IOException {
-    Acl.Builder b = getAcl(inheritId, fragmentName, isDirectEntry);
-    if (!isDirectory) {
-      b.setInheritanceType(InheritanceType.LEAF_NODE);
-    }
-    return b.build();
+  public Acl.Builder getAcl() throws IOException {
+    return getAcl(isDirectEntry);
   }
 
-  public Acl getInheritableByAllDescendentFoldersAcl(DocId inheritId,
-      String fragmentName) throws IOException {
-    return getAcl(inheritId, fragmentName,
-        isInheritableByAllDescendentFoldersEntry).build();
-  }
-
-  public Acl getInheritableByAllDescendentFilesAcl(DocId inheritId,
-      String fragmentName) throws IOException {
-    return getAcl(inheritId, fragmentName,
-        isInheritableByAllDescendentFilesEntry).build();
-  }
-
-  public Acl getInheritableByChildFoldersOnlyAcl(DocId inheritId,
-      String fragmentName) throws IOException {
-    return getAcl(inheritId, fragmentName,
-        isInheritableByChildFoldersOnlyEntry).build();
-  }
-
-  public Acl getInheritableByChildFilesOnlyAcl(DocId inheritId,
-      String fragmentName) throws IOException {
-    return getAcl(inheritId, fragmentName,
-        isInheritableByChildFilesOnlyEntry).build();
-  }
-
-  public Acl getShareAcl(DocId inheritId)
+  public Acl.Builder getInheritableByAllDescendentFoldersAcl()
       throws IOException {
-    // Windows NT share Acls are returned as inheritable by child files.
-    return getAcl(inheritId, null, isDirectEntry)
-        .setInheritanceType(InheritanceType.AND_BOTH_PERMIT).build();
+    return getAcl(isInheritableByAllDescendentFoldersEntry);
   }
 
-  private Acl.Builder getAcl(DocId inheritId, String fragmentName,
-      Predicate<Set<AclEntryFlag>> predicate) throws IOException {
+  public Acl.Builder getInheritableByAllDescendentFilesAcl()
+      throws IOException {
+    return getAcl(isInheritableByAllDescendentFilesEntry);
+  }
+
+  public Acl.Builder getInheritableByChildFoldersOnlyAcl() throws IOException {
+    return getAcl(isInheritableByChildFoldersOnlyEntry);
+  }
+
+  public Acl.Builder getInheritableByChildFilesOnlyAcl() throws IOException {
+    return getAcl(isInheritableByChildFilesOnlyEntry);
+  }
+
+  private Acl.Builder getAcl(Predicate<Set<AclEntryFlag>> predicate)
+      throws IOException {
     Set<Principal> permits = new HashSet<Principal>();
     Set<Principal> denies = new HashSet<Principal>();
     for (AclEntry entry : aclView.getAcl()) {
@@ -136,8 +116,7 @@
     }
 
     return new Acl.Builder().setPermits(permits).setDenies(denies)
-        .setInheritFrom(inheritId, fragmentName).setEverythingCaseInsensitive()
-        .setInheritanceType(InheritanceType.CHILD_OVERRIDES);
+        .setEverythingCaseInsensitive();
   }
 
   /**
diff --git a/src/com/google/enterprise/adaptor/fs/FsAdaptor.java b/src/com/google/enterprise/adaptor/fs/FsAdaptor.java
index 0693d61..c12f3b3 100644
--- a/src/com/google/enterprise/adaptor/fs/FsAdaptor.java
+++ b/src/com/google/enterprise/adaptor/fs/FsAdaptor.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Sets;
 import com.google.enterprise.adaptor.AbstractAdaptor;
 import com.google.enterprise.adaptor.Acl;
+import com.google.enterprise.adaptor.Acl.InheritanceType;
 import com.google.enterprise.adaptor.AdaptorContext;
 import com.google.enterprise.adaptor.Config;
 import com.google.enterprise.adaptor.DocId;
@@ -256,7 +257,8 @@
       AclBuilder builder = new AclBuilder(rootPath,
           delegate.getDfsShareAclView(rootPath.getParent()),
           supportedWindowsAccounts, builtinPrefix, namespace);
-      namedResources.put(DFS_SHARE_ACL_DOCID, builder.getShareAcl(null));
+      namedResources.put(DFS_SHARE_ACL_DOCID, builder.getAcl()
+          .setInheritanceType(InheritanceType.AND_BOTH_PERMIT).build());
 
       // Push the Acl for the active storage UNC path.
       Path activeStorage = delegate.getDfsUncActiveStorageUnc(rootPath);
@@ -266,16 +268,18 @@
       }
 
       builder = new AclBuilder(activeStorage,
-          delegate.getShareAclView(activeStorage), supportedWindowsAccounts,
-          builtinPrefix, namespace);
-      namedResources.put(SHARE_ACL_DOCID,
-          builder.getShareAcl(DFS_SHARE_ACL_DOCID));
+          delegate.getShareAclView(activeStorage),
+          supportedWindowsAccounts, builtinPrefix, namespace);
+      namedResources.put(SHARE_ACL_DOCID, builder.getAcl()
+          .setInheritFrom(DFS_SHARE_ACL_DOCID)
+          .setInheritanceType(InheritanceType.AND_BOTH_PERMIT).build());
     } else {
       // For a non-DFS UNC we have only have a share Acl to push.
       AclBuilder builder = new AclBuilder(rootPath,
-          delegate.getShareAclView(rootPath), supportedWindowsAccounts,
-          builtinPrefix, namespace);
-      namedResources.put(SHARE_ACL_DOCID, builder.getShareAcl(null));
+          delegate.getShareAclView(rootPath),
+          supportedWindowsAccounts, builtinPrefix, namespace);
+      namedResources.put(SHARE_ACL_DOCID, builder.getAcl()
+          .setInheritanceType(InheritanceType.AND_BOTH_PERMIT).build());
     }
 
     pusher.pushNamedResources(namedResources);
@@ -343,16 +347,20 @@
     if (isRoot || hasNoInheritedAcl) {
       builder = new AclBuilder(doc, aclViews.getCombinedAclView(),
           supportedWindowsAccounts, builtinPrefix, namespace);
-      acl = builder.getAcl(SHARE_ACL_DOCID, docIsDirectory, null);
+      acl = builder.getAcl().setInheritFrom(SHARE_ACL_DOCID)
+          .setInheritanceType(docIsDirectory ? InheritanceType.CHILD_OVERRIDES
+                              : InheritanceType.LEAF_NODE).build();
     } else {
       builder = new AclBuilder(doc, aclViews.getDirectAclView(),
           supportedWindowsAccounts, builtinPrefix, namespace);
       if (docIsDirectory) {
-        acl = builder.getAcl(parentDocId, docIsDirectory,
-                             CHILD_FOLDER_INHERIT_ACL);
+        acl = builder.getAcl()
+            .setInheritFrom(parentDocId, CHILD_FOLDER_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build();
       } else {
-        acl = builder.getAcl(parentDocId, docIsDirectory,
-                             CHILD_FILE_INHERIT_ACL);
+        acl = builder.getAcl()
+            .setInheritFrom(parentDocId, CHILD_FILE_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.LEAF_NODE).build();
       }
     }
     log.log(Level.FINEST, "Setting Acl: doc: {0}, acl: {1}",
@@ -362,29 +370,39 @@
     // Push the additional Acls for a folder.
     if (docIsDirectory) {
       if (isRoot || hasNoInheritedAcl) {
-        resp.putNamedResource(ALL_FOLDER_INHERIT_ACL,
-            builder.getInheritableByAllDescendentFoldersAcl(SHARE_ACL_DOCID,
-                                                           null));
+        resp.putNamedResource(ALL_FOLDER_INHERIT_ACL, 
+            builder.getInheritableByAllDescendentFoldersAcl()
+            .setInheritFrom(SHARE_ACL_DOCID)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(ALL_FILE_INHERIT_ACL,
-            builder.getInheritableByAllDescendentFilesAcl(SHARE_ACL_DOCID,
-                                                         null));
+            builder.getInheritableByAllDescendentFilesAcl()
+            .setInheritFrom(SHARE_ACL_DOCID)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(CHILD_FOLDER_INHERIT_ACL,
-            builder.getInheritableByChildFoldersOnlyAcl(SHARE_ACL_DOCID, null));
+            builder.getInheritableByChildFoldersOnlyAcl()
+            .setInheritFrom(SHARE_ACL_DOCID)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(CHILD_FILE_INHERIT_ACL,
-            builder.getInheritableByChildFilesOnlyAcl(SHARE_ACL_DOCID, null));
+            builder.getInheritableByChildFilesOnlyAcl()
+            .setInheritFrom(SHARE_ACL_DOCID)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
       } else {
-        resp.putNamedResource(ALL_FOLDER_INHERIT_ACL,
-            builder.getInheritableByAllDescendentFoldersAcl(parentDocId,
-                ALL_FOLDER_INHERIT_ACL));
+        resp.putNamedResource(ALL_FOLDER_INHERIT_ACL, 
+            builder.getInheritableByAllDescendentFoldersAcl()
+            .setInheritFrom(parentDocId, ALL_FOLDER_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(ALL_FILE_INHERIT_ACL,
-            builder.getInheritableByAllDescendentFilesAcl(parentDocId,
-                ALL_FILE_INHERIT_ACL));
+            builder.getInheritableByAllDescendentFilesAcl()
+            .setInheritFrom(parentDocId, ALL_FILE_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(CHILD_FOLDER_INHERIT_ACL,
-            builder.getInheritableByChildFoldersOnlyAcl(parentDocId,
-                ALL_FOLDER_INHERIT_ACL));
+            builder.getInheritableByChildFoldersOnlyAcl()
+            .setInheritFrom(parentDocId, ALL_FOLDER_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
         resp.putNamedResource(CHILD_FILE_INHERIT_ACL,
-            builder.getInheritableByChildFilesOnlyAcl(parentDocId,
-                ALL_FILE_INHERIT_ACL));
+            builder.getInheritableByChildFilesOnlyAcl()
+            .setInheritFrom(parentDocId, ALL_FILE_INHERIT_ACL)
+            .setInheritanceType(InheritanceType.CHILD_OVERRIDES).build());
       }
     }
 
diff --git a/src/com/google/enterprise/adaptor/fs/WindowsFileDelegate.java b/src/com/google/enterprise/adaptor/fs/WindowsFileDelegate.java
index 4b864ad..18b139b 100644
--- a/src/com/google/enterprise/adaptor/fs/WindowsFileDelegate.java
+++ b/src/com/google/enterprise/adaptor/fs/WindowsFileDelegate.java
@@ -51,6 +51,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -230,10 +231,17 @@
           + ". The path is not a valid directory.");
     }
 
+    CountDownLatch startSignal = new CountDownLatch(1);
     synchronized (monitorThreadLock) {
-      monitorThread = new MonitorThread(watchPath, queue);
+      monitorThread = new MonitorThread(watchPath, queue, startSignal);
       monitorThread.start();
     }
+    // Wait for the monitor thread to start watching filesystem.
+    try {
+      startSignal.await();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
   }
 
   @Override
@@ -249,13 +257,18 @@
   private static class MonitorThread extends Thread {
     private final Path watchPath;
     private final BlockingQueue<Path> queue;
+    private final CountDownLatch startSignal;
     private final HANDLE stopEvent;
 
-    public MonitorThread(Path watchPath, BlockingQueue<Path> queue) {
+    public MonitorThread(Path watchPath, BlockingQueue<Path> queue,
+        CountDownLatch startSignal) {
       Preconditions.checkNotNull(watchPath, "the watchPath may not be null");
       Preconditions.checkNotNull(queue, "the queue may not be null");
+      Preconditions.checkNotNull(startSignal,
+                                 "the start signal may not be null");
       this.watchPath = watchPath;
       this.queue = queue;
+      this.startSignal = startSignal;
       stopEvent = Kernel32.INSTANCE.CreateEvent(null, false, false, null);
     }
 
@@ -283,6 +296,9 @@
         runMonitorLoop();
       } catch (IOException e) {
         log.log(Level.WARNING, "Unable to monitor " + watchPath, e);
+      } finally {
+        // Wake up caller, in case monitor fails to start up.
+        startSignal.countDown();
       }
       log.exiting("WindowsFileDelegate", "MonitorThread.run", watchPath);
     }
@@ -360,6 +376,9 @@
               + ". GetLastError: " + klib.GetLastError());
         }
 
+        // Signal any waiting threads that the monitor is now active.
+        startSignal.countDown();
+
         log.log(Level.FINER, "Waiting for notifications.");
         int waitResult = klib.WaitForSingleObjectEx(stopEvent,
             Kernel32.INFINITE, true);
diff --git a/test/com/google/enterprise/adaptor/fs/AclBuilderTest.java b/test/com/google/enterprise/adaptor/fs/AclBuilderTest.java
index 1fb3414..b6ea1e2 100644
--- a/test/com/google/enterprise/adaptor/fs/AclBuilderTest.java
+++ b/test/com/google/enterprise/adaptor/fs/AclBuilderTest.java
@@ -29,8 +29,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.enterprise.adaptor.Acl;
-import com.google.enterprise.adaptor.Acl.InheritanceType;
-import com.google.enterprise.adaptor.DocId;
 import com.google.enterprise.adaptor.GroupPrincipal;
 import com.google.enterprise.adaptor.Principal;
 import com.google.enterprise.adaptor.UserPrincipal;
@@ -54,7 +52,6 @@
   public ExpectedException thrown = ExpectedException.none();
   
   private final Path doc = Paths.get("foo", "bar");
-  private final DocId inheritId = new DocId("foo");
   private final Set<String> windowsAccounts = ImmutableSet.of(
       "BUILTIN\\Administrators", "Everyone", "BUILTIN\\Users",
       "BUILTIN\\Guest", "NT AUTHORITY\\INTERACTIVE",
@@ -73,6 +70,8 @@
           .flags(FILE_INHERIT, DIRECTORY_INHERIT),
       group("sales").type(DENY).perms(GENERIC_READ)
           .flags(FILE_INHERIT, DIRECTORY_INHERIT));
+  // This is the expected ACL for the above aclView.
+  private final Acl expectedAcl = expectedBuilder().build();
 
   @Test
   public void testConstructorNullPath() throws Exception {
@@ -105,64 +104,32 @@
   }
 
   @Test
-  public void testGetAclForDirectory() throws Exception {
-    Acl acl = newBuilder(aclView).getAcl(inheritId, true, null);
-    Acl expected = expectedBuilder().build();
-    assertEquals(expected, acl);
-  }
-
-  @Test
-  public void testGetAclForFile() throws Exception {
-    Acl acl = newBuilder(aclView).getAcl(inheritId, false, null);
-    Acl expected = expectedBuilder()
-        .setInheritanceType(InheritanceType.LEAF_NODE)
-        .build();
-    assertEquals(expected, acl);
-  }
-
-  @Test
-  public void testGetShareAcl() throws Exception {
-    Acl acl = newBuilder(aclView).getShareAcl(inheritId);
-    Acl expected = expectedBuilder()
-        .setInheritanceType(InheritanceType.AND_BOTH_PERMIT)
-        .build();
-    assertEquals(expected, acl);
+  public void testGetAcl() throws Exception {
+    assertEquals(expectedAcl, newBuilder(aclView).getAcl().build());
   }
 
   @Test
   public void testGetInheritableByAllDescendentFoldersAcl() throws Exception {
-    String fragment = "allFoldersAcl";
-    Acl acl = newBuilder(aclView).getInheritableByAllDescendentFoldersAcl(
-        inheritId, fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    assertEquals(expectedAcl,
+        newBuilder(aclView).getInheritableByAllDescendentFoldersAcl().build());
   }
   
   @Test
   public void testGetInheritableByAllDescendentFilesAcl() throws Exception {
-    String fragment = "allFilesAcl";
-    Acl acl = newBuilder(aclView).getInheritableByAllDescendentFilesAcl(
-        inheritId, fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    assertEquals(expectedAcl,
+        newBuilder(aclView).getInheritableByAllDescendentFilesAcl().build());
   }
   
   @Test
   public void testGetInheritableByChildFoldersOnlyAcl() throws Exception {
-    String fragment = "childFoldersAcl";
-    Acl acl = newBuilder(aclView).getInheritableByChildFoldersOnlyAcl(
-        inheritId, fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    assertEquals(expectedAcl,
+        newBuilder(aclView).getInheritableByChildFoldersOnlyAcl().build());
   }
   
   @Test
   public void testGetInheritableByChildFilesOnlyAcl() throws Exception {
-    String fragment = "childFilesAcl";
-    Acl acl = newBuilder(aclView).getInheritableByChildFilesOnlyAcl(
-        inheritId, fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    assertEquals(expectedAcl,
+        newBuilder(aclView).getInheritableByChildFilesOnlyAcl().build());
   }
 
   @Test
@@ -181,31 +148,19 @@
           .flags(FILE_INHERIT));
     AclBuilder aclBuilder = newBuilder(aclView);
 
-    String fragment = "allFilesAcl";
-    Acl acl = aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId,
-        fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
-
-    fragment = "childFilesAcl";
-    acl = aclBuilder.getInheritableByChildFilesOnlyAcl(inheritId, fragment);
-    expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    // The file inherit ACLs should have all the users and groups.
+    assertEquals(expectedAcl,
+        aclBuilder.getInheritableByAllDescendentFilesAcl().build());
+    assertEquals(expectedAcl,
+        aclBuilder.getInheritableByChildFilesOnlyAcl().build());
 
     // The folder inherit ACLs should not include "mary" or "sales".
-    fragment = "allFoldersAcl";
-    acl = aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId,
-        fragment);
-    expected = expectedBuilder(fragment)
+    Acl expected = expectedBuilder()
         .setPermitUsers(users("joe")).setDenyGroups(emptyGroups).build();
-    assertEquals(expected, acl);
-
-    fragment = "childFoldersAcl";
-    acl = aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId,
-        fragment);
-    expected = expectedBuilder(fragment)
-        .setPermitUsers(users("joe")).setDenyGroups(emptyGroups).build();
-    assertEquals(expected, acl);
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFoldersAcl().build());
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFoldersAcl().build());
   }
 
   @Test
@@ -224,31 +179,19 @@
           .flags(DIRECTORY_INHERIT));
     AclBuilder aclBuilder = newBuilder(aclView);
 
-    String fragment = "allFoldersAcl";
-    Acl acl = aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId,
-        fragment);
-    Acl expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
-
-    fragment = "childFoldersAcl";
-    acl = aclBuilder.getInheritableByChildFoldersOnlyAcl(inheritId, fragment);
-    expected = expectedBuilder(fragment).build();
-    assertEquals(expected, acl);
+    // The folder inherit ACLs should have all the users and groups.
+    assertEquals(expectedAcl,
+        aclBuilder.getInheritableByAllDescendentFoldersAcl().build());
+    assertEquals(expectedAcl,
+        aclBuilder.getInheritableByChildFoldersOnlyAcl().build());
 
     // The file inherit ACLs should not include "mary" or "sales".
-    fragment = "allFilesAcl";
-    acl = aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId,
-        fragment);
-    expected = expectedBuilder(fragment)
+    Acl expected = expectedBuilder()
         .setPermitUsers(users("joe")).setDenyGroups(emptyGroups).build();
-    assertEquals(expected, acl);
-
-    fragment = "childFilesAcl";
-    acl = aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId,
-        fragment);
-    expected = expectedBuilder(fragment)
-        .setPermitUsers(users("joe")).setDenyGroups(emptyGroups).build();
-    assertEquals(expected, acl);
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFilesAcl().build());
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFilesAcl().build());
   }
 
   @Test
@@ -262,13 +205,12 @@
           .flags(FILE_INHERIT, DIRECTORY_INHERIT));
     AclBuilder aclBuilder = newBuilder(aclView);
 
-    Acl acl =
-        aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId, null);
+    Acl acl = aclBuilder.getInheritableByAllDescendentFoldersAcl().build();
     Acl expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mary")).build();
     assertEquals(expected, acl);
 
-    acl = aclBuilder.getInheritableByChildFoldersOnlyAcl(inheritId, null);
+    acl = aclBuilder.getInheritableByChildFoldersOnlyAcl().build();
     expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mike", "mary")).build();
     assertEquals(expected, acl);
@@ -285,13 +227,12 @@
           .flags(FILE_INHERIT, DIRECTORY_INHERIT));
     AclBuilder aclBuilder = newBuilder(aclView);
 
-    Acl acl =
-        aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId, null);
+    Acl acl = aclBuilder.getInheritableByAllDescendentFilesAcl().build();
     Acl expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mary")).build();
     assertEquals(expected, acl);
 
-    acl = aclBuilder.getInheritableByChildFilesOnlyAcl(inheritId, null);
+    acl = aclBuilder.getInheritableByChildFilesOnlyAcl().build();
     expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mike", "mary")).build();
     assertEquals(expected, acl);
@@ -309,18 +250,17 @@
     AclBuilder aclBuilder = newBuilder(aclView);
 
     // This node's ACL should not include mike.
-    Acl acl = aclBuilder.getAcl(inheritId, true, null);
     Acl expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mary")).build();
-    assertEquals(expected, acl);
+    assertEquals(expected, aclBuilder.getAcl().build());
                          
     // However, all of its children should include mike.
     expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mike", "mary")).build();
-    acl = aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId, null);
-    assertEquals(expected, acl);
-    acl = aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId, null);
-    assertEquals(expected, acl);
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFoldersAcl().build());
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFilesAcl().build());
   }
 
   @Test
@@ -335,12 +275,11 @@
     // This node's ACLs should not include mike.
     Acl expected = emptyExpectedBuilder()
         .setPermitUsers(users("joe")).build();
-    Acl acl = aclBuilder.getAcl(inheritId, true, null);
-    assertEquals(expected, acl);
-    acl = aclBuilder.getInheritableByAllDescendentFoldersAcl(inheritId, null);
-    assertEquals(expected, acl);
-    acl = aclBuilder.getInheritableByAllDescendentFilesAcl(inheritId, null);
-    assertEquals(expected, acl);
+    assertEquals(expected, aclBuilder.getAcl().build());
+    assertEquals(expected, 
+        aclBuilder.getInheritableByAllDescendentFoldersAcl().build());
+    assertEquals(expected,
+        aclBuilder.getInheritableByAllDescendentFilesAcl().build());
   }
 
   @Test
@@ -365,8 +304,7 @@
     Acl expected = emptyExpectedBuilder()
         .setPermitUsers(users(Iterables.toArray(windowsAccounts, String.class)))
         .build();
-    Acl acl = aclBuilder.getAcl(inheritId, true, null);
-    assertEquals(expected, acl);    
+    assertEquals(expected, aclBuilder.getAcl().build());
   }
 
   /** Returns an AclBuilder for the AclFileAttributeView. */
@@ -381,16 +319,7 @@
    * then call build().
    */
   private Acl.Builder expectedBuilder() {
-    return expectedBuilder(null);
-  }
-
-  /**
-   * Returns an Acl.Builder representing the aclView field.
-   * The caller is expected to overwrite any of thes presets,
-   * then call build().
-   */
-  private Acl.Builder expectedBuilder(String fragment) {
-    return emptyExpectedBuilder(fragment)
+    return emptyExpectedBuilder()
         .setPermitUsers(users("joe", "mary")).setDenyUsers(users("mike"))
         .setPermitGroups(groups("EVERYONE")).setDenyGroups(groups("sales"));
   }
@@ -399,16 +328,7 @@
    * Returns an Acl.Builder with no users or groups.
    */
   private Acl.Builder emptyExpectedBuilder() {
-    return emptyExpectedBuilder(null);
-  }
-
-  /**
-   * Returns an Acl.Builder with no users or groups.
-   */
-  private Acl.Builder emptyExpectedBuilder(String fragment) {
-    return new Acl.Builder().setInheritFrom(inheritId, fragment)
-        .setInheritanceType(InheritanceType.CHILD_OVERRIDES)
-        .setEverythingCaseInsensitive();
+    return new Acl.Builder().setEverythingCaseInsensitive();
   }
 
   /**
diff --git a/test/com/google/enterprise/adaptor/fs/WindowsFileDelegateTest.java b/test/com/google/enterprise/adaptor/fs/WindowsFileDelegateTest.java
new file mode 100644
index 0000000..f345598
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/fs/WindowsFileDelegateTest.java
@@ -0,0 +1,291 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.fs;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import org.junit.*;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link WindowsFileDelegate} */
+public class WindowsFileDelegateTest {
+
+  @BeforeClass
+  public static void checkIfRunningOnWindows() {
+    TestHelper.assumeOsIsWindows();
+  }
+
+  private FileDelegate delegate = new WindowsFileDelegate();
+  private BlockingQueue<Path> queue = new LinkedBlockingQueue<Path>();
+  private Path tempRoot;
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Before
+  public void setUp() throws Exception {
+    tempRoot = temp.getRoot().getCanonicalFile().toPath();
+  }
+    
+  @After
+  public void tearDown() {
+    delegate.destroy();
+  }
+
+  private Path newTempDir(String name) throws IOException {
+    return temp.newFolder(name).toPath();
+  }
+
+  private Path newTempFile(String name) throws IOException {
+    return temp.newFile(name).toPath();
+  }
+
+  private Path newTempFile(Path parent, String name) throws IOException {
+    Preconditions.checkArgument(parent.startsWith(tempRoot));
+    return Files.createFile(parent.resolve(name));
+  }
+
+  @Test
+  public void testNewDocIdLocalFiles() throws Exception {
+    Path dir = newTempDir("testDir");
+    Path file = newTempFile(dir, "test");
+
+    String id = delegate.newDocId(tempRoot).getUniqueId();
+    assertTrue(id.startsWith(tempRoot.toString().replace('\\', '/')));
+    assertTrue(id.endsWith("/"));
+
+    id = delegate.newDocId(dir).getUniqueId();
+    assertTrue(id.startsWith(tempRoot.toString().replace('\\', '/')));
+    assertTrue(id.startsWith(dir.toString().replace('\\', '/')));
+    assertTrue(id.endsWith("/"));
+
+    id = delegate.newDocId(file).getUniqueId();
+    assertTrue(id.startsWith(tempRoot.toString().replace('\\', '/')));
+    assertTrue(id.startsWith(dir.toString().replace('\\', '/')));
+    assertTrue(id.equals(file.toString().replace('\\', '/')));
+    assertFalse(id.endsWith("/"));
+  }
+
+  @Test
+  public void testNewDocIdVirtualUncPaths() throws Exception {
+    assertEquals("\\\\host/share",
+        delegate.newDocId(Paths.get("\\\\host\\share")).getUniqueId());
+    assertEquals("\\\\host/share",
+        delegate.newDocId(Paths.get("\\\\host\\share\\")).getUniqueId());
+    assertEquals("\\\\host/share/foo/bar",
+        delegate.newDocId(Paths.get("\\\\host\\share\\foo\\bar"))
+        .getUniqueId());
+  }
+
+  @Test
+  public void testNewDocIdLocalUncPaths() throws Exception {
+    String uncTempRoot = getTempRootAsUncPath();
+    assumeNotNull(uncTempRoot);
+    Path tempRoot = Paths.get(uncTempRoot);
+    String expectedTempRootId = "\\\\" + uncTempRoot.substring(2)
+        .replace('\\', '/') + "/";
+
+    assertEquals(expectedTempRootId,
+        delegate.newDocId(tempRoot).getUniqueId());
+
+    newTempDir("testDir");
+    assertEquals(expectedTempRootId + "testDir/",
+        delegate.newDocId(tempRoot.resolve("testDir")).getUniqueId());
+
+    newTempFile("test");
+    assertEquals(expectedTempRootId + "test",
+        delegate.newDocId(tempRoot.resolve("test")).getUniqueId());
+  }
+
+  private String getTempRootAsUncPath() throws IOException {
+    String tempPath = temp.getRoot().getCanonicalPath();
+    if (tempPath.length() > 2 && tempPath.charAt(1) == ':') {
+      String uncPath = "\\\\localhost\\" + tempPath.substring(0, 1) + "$"
+          + tempPath.substring(2);
+      try {
+        // Now verify we have access to the local administrative share.
+        if (new File(uncPath).list() != null) {
+          return uncPath;
+        }
+      } catch (SecurityException e) {
+        // Cannot access local administrative share.
+      }
+    }
+    return null;
+  }
+
+  @Test
+  public void testStartMonitorBadPath() throws Exception {
+    Path file = newTempFile("test.txt");
+    thrown.expect(IOException.class);
+    delegate.startMonitorPath(file, queue);
+  }
+
+  @Test
+  public void testStartStopMonitor() throws Exception {
+    delegate.startMonitorPath(tempRoot, queue);
+    delegate.stopMonitorPath();
+  }
+
+  @Test
+  public void testMonitorAddFile() throws Exception {
+    // These shouldn't show up as new or modified.
+    newTempDir("existingDir");
+    newTempFile("existingFile");
+    delegate.startMonitorPath(tempRoot, queue);
+    Path file = newTempFile("test.txt");
+    // Adding a file shows up as a change to its parent.
+    checkForChanges(Collections.singleton(tempRoot));
+  }
+
+  @Test
+  public void testMonitorDeleteFile() throws Exception {
+    Path file = newTempFile("test.txt");
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.delete(file);
+    // Deleting a file shows up as a change to itself and its parent.
+    checkForChanges(Sets.newHashSet(tempRoot, file));
+  }
+
+  @Test
+  public void testMonitorRenameFile() throws Exception {
+    Path file = newTempFile("test.txt");
+    Path newFile = file.resolveSibling("newName.txt");
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.move(file, newFile, StandardCopyOption.ATOMIC_MOVE);
+    // Renaming a file shows up as a change to its old name, its new name,
+    // and its parent.
+    checkForChanges(Sets.newHashSet(tempRoot, file, newFile));
+  }
+
+  @Test
+  public void testMonitorMoveAccrossDirs() throws Exception {
+    Path dir1 = newTempDir("dir1");
+    Path dir2 = newTempDir("dir2");
+    Path file1 = newTempFile(dir1, "test.txt");
+    Path file2 = dir2.resolve(file1.getFileName());
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.move(file1, file2);
+    // Moving a file shows up as a change to its old name, its new name,
+    // its old parent, and its new parent.
+    checkForChanges(Sets.newHashSet(file1, file2, dir1, dir2));
+  }
+
+  @Test
+  public void testMonitorModifyFile() throws Exception {
+    Path file = newTempFile("test.txt");
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.write(file, "Hello World".getBytes("UTF-8"));
+    // Modifying a file shows up as a change to that file.
+    checkForChanges(Collections.singleton(file));
+  }
+
+  @Test
+  public void testMonitorModifyFileAttributes() throws Exception {
+    Path file = newTempFile("test.txt");
+    FileTime lastModified = Files.getLastModifiedTime(file);
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.setLastModifiedTime(file, 
+        FileTime.fromMillis(lastModified.toMillis() + 10000L));
+    // Modifying a file shows up as a change to that file.
+    checkForChanges(Collections.singleton(file));
+  }
+
+  @Test
+  public void testMonitorRenameDir() throws Exception {
+    Path dir = newTempDir("dir1");
+    Path newDir = dir.resolveSibling("newName.dir");
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.move(dir, newDir, StandardCopyOption.ATOMIC_MOVE);
+    // Renaming a directory shows up as a change to its old name, its new name,
+    // and its parent.
+    checkForChanges(Sets.newHashSet(tempRoot, dir));
+  }
+
+  @Test
+  public void testMonitorMoveDir() throws Exception {
+    Path dir1 = newTempDir("dir1");
+    Path dir2 = newTempDir("dir2");
+    Path dir1dir2 = dir1.resolve(dir2.getFileName());
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.move(dir2, dir1dir2);
+    // Moving a file shows up as a change to its old name, its new name,
+    // its old parent, and its new parent.
+    checkForChanges(Sets.newHashSet(tempRoot, dir1, dir2));
+  }
+
+  @Test
+  public void testMonitorChangesInSubDirs() throws Exception {
+    Path dir = newTempDir("testDir");
+    Path file = newTempFile(dir, "test.txt");
+    delegate.startMonitorPath(tempRoot, queue);
+    Files.write(file, "Hello World".getBytes("UTF-8"));
+    // Modifying a file shows up as a change to that file.
+    checkForChanges(Collections.singleton(file));
+  }
+
+  private void checkForChanges(Set<Path> expected) throws Exception {
+    // Collect up the changes.  Adapted from BlockingQueueBatcher.take(),
+    // but without infinite initial wait.
+    Set<Path> changes = Sets.newHashSet();
+    int maxBatchSize = expected.size();
+    long maxLatencyMillis = 10000;
+    long currentTime = System.currentTimeMillis();
+    long stopBatchTime = currentTime + maxLatencyMillis;
+
+    while (currentTime < stopBatchTime && changes.size() < maxBatchSize) {
+      // Block until an item is in the queue or the batch timeout expires.
+      Path path =
+          queue.poll(stopBatchTime - currentTime, TimeUnit.MILLISECONDS);
+      if (path == null) {
+        // Timeout occurred.
+        break;
+      }
+      changes.add(path);
+      queue.drainTo(changes);
+      currentTime = System.currentTimeMillis();
+    }
+
+    // Now verify that the changes we got were the ones that were expected.
+    assertEquals(expected, changes);
+  }
+}