Introduce AclPopulator
diff --git a/src/com/google/enterprise/adaptor/experimental/AclPopulator.java b/src/com/google/enterprise/adaptor/experimental/AclPopulator.java
new file mode 100644
index 0000000..1d58ec5
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/experimental/AclPopulator.java
@@ -0,0 +1,215 @@
+// 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.experimental;
+
+import com.google.enterprise.adaptor.AbstractAdaptor;
+import com.google.enterprise.adaptor.Acl;
+import com.google.enterprise.adaptor.DocId;
+import com.google.enterprise.adaptor.DocIdPusher;
+import com.google.enterprise.adaptor.Request;
+import com.google.enterprise.adaptor.Response;
+import com.google.enterprise.adaptor.UserPrincipal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.logging.Logger;
+
+/**
+ * Tool to populate GSA with lots of ACLs.
+ */
+public class AclPopulator extends AbstractAdaptor {
+  private static final Logger log
+      = Logger.getLogger(AclPopulator.class.getName());
+  private Charset encoding = Charset.forName("UTF-8");
+
+  private ThreadLocal<Random> rnd = new ThreadLocal<Random>() {
+    public Random initialValue() {
+      return new Random();
+    }
+  };
+
+  @Override
+  public void getDocIds(DocIdPusher pusher) throws InterruptedException {
+  }
+
+  private static final String TOP_LEVEL_DIRS[] = new String[] {
+      "eng",
+      "pm",
+      "qa",
+      "googlers",
+      "test",
+      "abcde",
+      "enterprise",
+      "ops",
+      "cowboys",
+      "cowgirls"
+  };
+  private static final String SPECIAL_PERMIT_DIR = "googlers/";
+
+  @Override
+  public void getDocContent(Request req, Response resp) throws IOException {
+    DocId id = req.getDocId();
+    String uniqueId = id.getUniqueId();
+
+    if ("".equals(uniqueId)) {
+      String content = makeTopLevelIndexFile();
+      Acl.Builder aclBuilder = new Acl.Builder()
+          .setInheritanceType(Acl.InheritanceType.CHILD_OVERRIDES)
+          .setPermitUsers(Arrays.asList(new UserPrincipal("vin")))
+          .setDenyUsers(Arrays.asList(new UserPrincipal("joker")));
+      resp.setAcl(aclBuilder.build());
+
+      resp.setContentType("text/html");
+      OutputStream os = resp.getOutputStream();
+      os.write(content.getBytes(encoding));
+      return;
+    }
+
+    ensureValidId(uniqueId);
+
+    // make index.html or make content
+    String content = null;
+    String parts[] = uniqueId.split("/", 0); // drop trailing empties
+    DocId parentId = makeParentId(parts); // ends with slash; parent is a dir
+
+    Acl.Builder aclBuilder = new Acl.Builder()
+        .setInheritFrom(parentId)
+        .setInheritanceType(Acl.InheritanceType.CHILD_OVERRIDES)
+        .setPermitUsers(Arrays.asList(new UserPrincipal("vin")))
+        .setDenyUsers(Arrays.asList(new UserPrincipal("joker")));
+
+    if (!uniqueId.endsWith("/")) {
+      // is file
+      if (uniqueId.contains(SPECIAL_PERMIT_DIR)) {
+        aclBuilder.setPermitUsers(Arrays.asList(new UserPrincipal("siyu")));
+      }
+      content = makeContent();
+      resp.setContentType("text/plain; charset=utf-8");
+    } else {
+      // is directory
+      switch(parts.length) {  // number of parts with empties at end stripped
+        case 1:
+          content = makeSubfolders(3);
+          break;
+        case 2:
+          content = makeSubfolders(2);
+          break;
+        case 3:
+          content = makeFiles(4);
+          break;
+        default:
+          throw new IllegalStateException("bad id: " + uniqueId);
+      }
+      resp.setContentType("text/html");
+    }
+
+    resp.setAcl(aclBuilder.build());
+    OutputStream os = resp.getOutputStream();
+    os.write(content.getBytes(encoding));
+  }
+
+  private String makeSubfolders(int numOfFolders) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<body>\n");
+    for (int i = 0; i < numOfFolders; ++i) {
+      int filename = i;
+      sb.append("<a href=\"")
+          .append(filename)
+          .append("/")
+          .append("\">")
+          .append(filename)
+          .append("</a></br>\n");
+    }
+    sb.append("</body>\n");
+    return sb.toString();
+  }
+
+  private String makeFiles(int numOfFiles) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<body>\n");
+    for (int i = 0; i < numOfFiles; ++i) {
+      int filename = i;
+      sb.append("<a href=\"")
+          .append(filename)
+          .append("\">")
+          .append(filename)
+          .append("</a></br>\n");
+    }
+    sb.append("</body>\n");
+    return sb.toString();
+  }
+
+  private String makeContent() {
+    StringBuilder sb = new StringBuilder();
+    int tenBillion = 1000 * 1000 * 1000;
+    for (int i = 0; i < 5000; ++i) {
+      sb.append(rnd.get().nextInt(tenBillion));
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+
+  private String makeTopLevelIndexFile() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<body>\n");
+    for (String topLevelDir : TOP_LEVEL_DIRS) {
+      sb.append("<a href=\"")
+          .append(topLevelDir)
+          .append("/")
+          .append("\">")
+          .append(topLevelDir)
+          .append("/")
+          .append("</a></br>\n");
+    }
+    sb.append("</body>\n");
+    return sb.toString();
+  }
+
+  private DocId makeParentId(String[] parts) {
+    if (parts.length <= 1) {
+      return new DocId("");
+    }
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < parts.length - 1; ++i) {
+      sb.append(parts[i]).append("/");
+    }
+    // keep last slash, because parent is always a directory
+    return new DocId(sb.toString());
+  }
+
+  private static void ensureValidId(String id) {
+    // make sure doc id makes sense; we know it is not root.
+    // examples of valid ids:
+    //    "eng/"
+    //    "test/15/"
+    //    "test/15/10/"
+    //    "test/15/10/filename"
+    String parts[] = id.split("/", -1);
+    switch(parts.length) {  // number of parts with empties at end kept
+      case 2:
+      case 3:
+      case 4: break;
+      default: throw new IllegalStateException("bad id: " + id);
+    }
+  }
+
+  /** Call default main for adaptors. */
+  public static void main(String[] args) {
+    AbstractAdaptor.main(new AclPopulator(), args);
+  }
+}