Initial subversion import.

The following lines are to recognize contributions made before the switch
to Subversion.

Patch by: bruce, jgw, scottb, mmendez, ecc, hcc, knorton, haeberling, samgross, mat.gessel


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build-tools/doctool/src/booklet.xsd b/build-tools/doctool/src/booklet.xsd
new file mode 100644
index 0000000..f1daf8f
--- /dev/null
+++ b/build-tools/doctool/src/booklet.xsd
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
+	<xs:element name="booklet">
+		<xs:annotation>
+			<xs:documentation>The documentation for the entire API</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:element ref="package" maxOccurs="unbounded"/>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="package">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:choice minOccurs="0" maxOccurs="unbounded">
+					<xs:element ref="class"/>
+					<xs:element ref="interface"/>
+				</xs:choice>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="class">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:group ref="access" minOccurs="0"/>
+				<xs:sequence>
+					<xs:element name="isAbstract" minOccurs="0"/>
+					<xs:element name="isStatic" minOccurs="0"/>
+					<xs:element name="isFinal" minOccurs="0"/>
+				</xs:sequence>
+				<xs:sequence>
+					<xs:element name="superclass" minOccurs="0">
+						<xs:complexType>
+							<xs:simpleContent>
+								<xs:extension base="xs:string">
+									<xs:attribute name="ref">
+										<xs:annotation>
+											<xs:documentation>The id of the type of the superclass</xs:documentation>
+										</xs:annotation>
+									</xs:attribute>
+								</xs:extension>
+							</xs:simpleContent>
+						</xs:complexType>
+					</xs:element>
+					<xs:element ref="superinterface" minOccurs="0" maxOccurs="unbounded"/>
+				</xs:sequence>
+				<xs:choice minOccurs="0" maxOccurs="unbounded">
+					<xs:element ref="field"/>
+					<xs:element ref="method"/>
+					<xs:element ref="class"/>
+					<xs:element ref="interface"/>
+					<xs:element ref="constructor"/>
+				</xs:choice>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="interface">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:group ref="access" minOccurs="0"/>
+				<xs:sequence>
+					<xs:element name="isAbstract" minOccurs="0">
+						<xs:annotation>
+							<xs:documentation>Implied but harmless</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="isStatic" minOccurs="0">
+						<xs:annotation>
+							<xs:documentation>Implied but harmless on a nested interface</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+				</xs:sequence>
+				<xs:element ref="superinterface" minOccurs="0" maxOccurs="unbounded"/>
+				<xs:choice minOccurs="0" maxOccurs="unbounded">
+					<xs:element ref="field"/>
+					<xs:element ref="method"/>
+					<xs:element ref="class"/>
+					<xs:element ref="interface"/>
+				</xs:choice>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="method">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:group ref="access" minOccurs="0"/>
+				<xs:choice minOccurs="0">
+					<xs:element name="isAbstract"/>
+					<xs:element name="isStatic">
+						<xs:annotation>
+							<xs:documentation>N/A when the method appears in an interface</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="isFinal">
+						<xs:annotation>
+							<xs:documentation>N/A when the method appears in an interface</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+				</xs:choice>
+				<xs:element ref="flatSignature"/>
+				<xs:element name="isSynchronized" minOccurs="0"/>
+				<xs:element name="type">
+					<xs:annotation>
+						<xs:documentation>The method return type</xs:documentation>
+					</xs:annotation>
+				</xs:element>
+				<xs:element ref="params"/>
+				<xs:element ref="throws" minOccurs="0"/>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="field">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:group ref="access" minOccurs="0"/>
+				<xs:sequence>
+					<xs:element name="isStatic" minOccurs="0"/>
+					<xs:element name="isFinal" minOccurs="0"/>
+				</xs:sequence>
+				<xs:element name="type">
+					<xs:annotation>
+						<xs:documentation>The field type</xs:documentation>
+					</xs:annotation>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="link">
+		<xs:annotation>
+			<xs:documentation>An explicit link to an id in this same document format (relies on special treatment from XSLT)</xs:documentation>
+		</xs:annotation>
+		<xs:complexType mixed="true">
+			<xs:attribute name="ref">
+				<xs:annotation>
+					<xs:documentation>The link target id</xs:documentation>
+				</xs:annotation>
+			</xs:attribute>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="superinterface">
+		<xs:complexType>
+			<xs:simpleContent>
+				<xs:extension base="xs:string">
+					<xs:attribute name="ref">
+						<xs:annotation>
+							<xs:documentation>The id of the interface type</xs:documentation>
+						</xs:annotation>
+					</xs:attribute>
+				</xs:extension>
+			</xs:simpleContent>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="params">
+		<xs:annotation>
+			<xs:documentation>Method/constructor parameters in left-to-right order</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="param" minOccurs="0" maxOccurs="unbounded">
+					<xs:complexType>
+						<xs:sequence>
+							<xs:element name="type">
+								<xs:annotation>
+									<xs:documentation>The param type, as it should appear to the reader</xs:documentation>
+								</xs:annotation>
+								<xs:complexType>
+									<xs:simpleContent>
+										<xs:extension base="xs:string">
+											<xs:attribute name="ref">
+												<xs:annotation>
+													<xs:documentation>A reference to the type id of the param type</xs:documentation>
+												</xs:annotation>
+											</xs:attribute>
+										</xs:extension>
+									</xs:simpleContent>
+								</xs:complexType>
+							</xs:element>
+							<xs:element name="name" type="xs:string">
+								<xs:annotation>
+									<xs:documentation>The name of the param</xs:documentation>
+								</xs:annotation>
+							</xs:element>
+						</xs:sequence>
+					</xs:complexType>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="flatSignature">
+		<xs:annotation>
+			<xs:documentation>A simple param signature enclosed in parentheses that would differentitate overloads in a table of mehod links, such as "(int, Widget)"</xs:documentation>
+		</xs:annotation>
+	</xs:element>
+	<xs:group name="sectionHead">
+		<xs:sequence>
+			<xs:element name="id">
+				<xs:annotation>
+					<xs:documentation>The id of this section, which can be the target of a link and which should be unique across all documents which might want to link to this section </xs:documentation>
+				</xs:annotation>
+			</xs:element>
+			<xs:element name="name">
+				<xs:annotation>
+					<xs:documentation>The name/title of this section; if a @title tag is found, it should be used rather than the programmatic name</xs:documentation>
+				</xs:annotation>
+			</xs:element>
+			<xs:element name="location" minOccurs="0">
+				<xs:annotation>
+					<xs:documentation>TODO - replace me with better XSLT - A sequence of links that indicate structure back to the logical parent of this component within the doc hierarchy</xs:documentation>
+				</xs:annotation>
+				<xs:complexType>
+					<xs:sequence minOccurs="0" maxOccurs="unbounded">
+						<xs:element ref="link"/>
+					</xs:sequence>
+				</xs:complexType>
+			</xs:element>
+			<xs:element name="lead">
+				<xs:annotation>
+					<xs:documentation>Simple HTML intro text (a single phrase or sentence) that might appear in an overview table</xs:documentation>
+				</xs:annotation>
+				<xs:complexType mixed="true">
+					<xs:sequence minOccurs="0" maxOccurs="unbounded">
+						<xs:any namespace="##any" processContents="skip"/>
+					</xs:sequence>
+				</xs:complexType>
+			</xs:element>
+			<xs:element name="description">
+				<xs:annotation>
+					<xs:documentation>Detailed narrative related to the topic</xs:documentation>
+				</xs:annotation>
+				<xs:complexType mixed="true">
+					<xs:sequence minOccurs="0" maxOccurs="unbounded">
+						<xs:any namespace="##any" processContents="skip">
+							<xs:annotation>
+								<xs:documentation>HTML description; "link" elements inteded to be parsed and fixed up</xs:documentation>
+							</xs:annotation>
+						</xs:any>
+					</xs:sequence>
+				</xs:complexType>
+			</xs:element>
+			<xs:element name="tags" minOccurs="0">
+				<xs:annotation>
+					<xs:documentation>Arbitrary tags for which a particular XSLT might handle specially such as "param", "returns", "throws"; some well-known ones are included for reference</xs:documentation>
+				</xs:annotation>
+				<xs:complexType>
+					<xs:sequence minOccurs="0" maxOccurs="unbounded">
+						<xs:any namespace="##any" processContents="skip">
+							<xs:annotation>
+								<xs:documentation>@link or any out-of-band tag, such as those found in JavaDoc, go here for potential XSLT special treatment</xs:documentation>
+							</xs:annotation>
+						</xs:any>
+					</xs:sequence>
+				</xs:complexType>
+			</xs:element>
+		</xs:sequence>
+	</xs:group>
+	<xs:group name="access">
+		<xs:choice>
+			<xs:element name="isPublic"/>
+			<xs:element name="isProtected"/>
+			<xs:element name="isPackagePrivate"/>
+			<xs:element name="isPrivate"/>
+		</xs:choice>
+	</xs:group>
+	<xs:element name="constructor">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:group ref="sectionHead"/>
+				<xs:group ref="access" minOccurs="0"/>
+				<xs:element ref="flatSignature"/>
+				<xs:element ref="params"/>
+				<xs:element ref="throws" minOccurs="0"/>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="throws">
+		<xs:annotation>
+			<xs:documentation>A list of exception that might be thrown</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence minOccurs="0" maxOccurs="unbounded">
+				<xs:element name="throw">
+					<xs:annotation>
+						<xs:documentation>Indicates a method/ctor might throw this type</xs:documentation>
+					</xs:annotation>
+					<xs:complexType mixed="true">
+						<xs:attribute name="ref">
+							<xs:annotation>
+								<xs:documentation>A reference to the id of the class that can be thrown</xs:documentation>
+							</xs:annotation>
+						</xs:attribute>
+					</xs:complexType>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+</xs:schema>
diff --git a/build-tools/doctool/src/com/google/doctool/Booklet.java b/build-tools/doctool/src/com/google/doctool/Booklet.java
new file mode 100644
index 0000000..5ed1e03
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/Booklet.java
@@ -0,0 +1,944 @@
+// Copyright 2006 Google Inc. All Rights Reserved.
+package com.google.doctool;
+
+import com.google.doctool.LinkResolver.ExtraClassResolver;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.ConstructorDoc;
+import com.sun.javadoc.Doc;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.ExecutableMemberDoc;
+import com.sun.javadoc.FieldDoc;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.PackageDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.ProgramElementDoc;
+import com.sun.javadoc.RootDoc;
+import com.sun.javadoc.SeeTag;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+import com.sun.javadoc.Type;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Stack;
+
+public class Booklet {
+
+  private static final String OPT_BKCODE = "-bkcode";
+  private static final String OPT_BKDOCPKG = "-bkdocpkg";
+  private static final String OPT_BKOUT = "-bkout";
+
+  private static Booklet sBooklet;
+
+  public static void main(String[] args) {
+    // Strip off our arguments at the beginning.
+    //
+    com.sun.tools.javadoc.Main.execute(args);
+  }
+
+  public static int optionLength(String option) {
+    if (option.equals(OPT_BKOUT))
+      return 2;
+    else if (option.equals(OPT_BKDOCPKG))
+      return 2;
+    else if (option.equals(OPT_BKCODE))
+      return 1;
+    return 0;
+  }
+
+  public static String slurpSource(SourcePosition position) {
+    FileReader fr = null;
+    try {
+      fr = new FileReader(position.file());
+      BufferedReader br = new BufferedReader(fr);
+      for (int i = 0, n = position.line() - 1; i < n; ++i)
+        br.readLine();
+
+      StringBuffer lines = new StringBuffer();
+      String line = br.readLine();
+      int braceDepth = 0;
+      int indent = -1;
+      boolean seenSemiColonOrBrace = false;
+      while (line != null) {
+        if (indent == -1) {
+          for (indent = 0; Character.isWhitespace(line.charAt(indent)); ++indent);
+        }
+
+        if (line.length() >= indent)
+          line = line.substring(indent);
+
+        lines.append(line + "\n");
+        for (int i = 0, n = line.length(); i < n; ++i) {
+          char c = line.charAt(i);
+          if (c == '{') {
+            seenSemiColonOrBrace = true;
+            ++braceDepth;
+          } else if (c == '}') {
+            --braceDepth;
+          } else if (c == ';') {
+            seenSemiColonOrBrace = true;
+          }
+        }
+
+        if (braceDepth > 0 || !seenSemiColonOrBrace) {
+          line = br.readLine();
+        } else {
+          break;
+        }
+      }
+
+      String code = lines.toString();
+      return code;
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      try {
+        if (fr != null)
+          fr.close();
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+    return "";
+  }
+
+  public static boolean start(RootDoc rootDoc) {
+    getBooklet().process(rootDoc);
+    return true;
+  }
+
+  public static boolean validOptions(String[][] options,
+      DocErrorReporter reporter) {
+    return getBooklet().analyzeOptions(options, reporter);
+  }
+
+  private static Booklet getBooklet() {
+    if (sBooklet == null)
+      sBooklet = new Booklet();
+    return sBooklet;
+  }
+
+  public Booklet() {
+    // Set up standard tags (to ignore during tag processing)
+    //
+    fStandardTagKinds.add("@see");
+    fStandardTagKinds.add("@serial");
+    fStandardTagKinds.add("@throws");
+    fStandardTagKinds.add("@param");
+    fStandardTagKinds.add("@id");
+  }
+
+  private boolean analyzeOptions(String[][] options, DocErrorReporter reporter) {
+    for (int i = 0, n = options.length; i < n; ++i) {
+      if (options[i][0].equals(OPT_BKOUT)) {
+        fOutputPath = options[i][1];
+      } else if (options[i][0].equals(OPT_BKDOCPKG)) {
+        String[] packages = options[i][1].split(";");
+        fPackagesToGenerate = new HashSet();
+        for (int packageIndex = 0; packageIndex < packages.length; ++packageIndex) {
+          fPackagesToGenerate.add(packages[packageIndex]);
+        }
+      } else if (options[i][0].equals(OPT_BKCODE)) {
+        fShowCode = true;
+      }
+    }
+
+    if (fOutputPath == null) {
+      reporter.printError("You must specify an output directory with "
+        + OPT_BKOUT);
+      return false;
+    }
+
+    return true;
+  }
+
+  private void begin(String tag) {
+    pw.print("<" + tag + ">");
+    fTagStack.push(tag);
+  }
+
+  private void begin(String tag, String attr, String value) {
+    pw.print("<" + tag + " " + attr + "='" + value + "'>");
+    fTagStack.push(tag);
+  }
+
+  private void beginCDATA() {
+    pw.print("<![CDATA[");
+  }
+
+  private void beginEndln(String tag) {
+    pw.println("<" + tag + "/>");
+  }
+
+  private void beginln(String tag) {
+    pw.println();
+    begin(tag);
+  }
+
+  private void beginln(String tag, String attr, String value) {
+    pw.println();
+    begin(tag, attr, value);
+  }
+
+  private void emitDescription(ClassDoc enclosing, Doc forWhat,
+      Tag[] leadInline, Tag[] descInline) {
+    emitJRELink(enclosing, forWhat);
+
+    beginln("lead");
+    processTags(leadInline);
+    endln();
+
+    beginln("description");
+    processTags(descInline);
+    endln();
+  }
+
+  private void emitIdentity(String id, String name) {
+    beginln("id");
+    text(id);
+    endln();
+
+    beginln("name");
+    text(name);
+    endln();
+  }
+
+  private void emitJRELink(ClassDoc enclosing, Doc doc) {
+    String jreLink = "http://java.sun.com/j2se/1.4.2/docs/api/";
+    if (doc instanceof ClassDoc) {
+      ClassDoc classDoc = (ClassDoc) doc;
+      String pkg = classDoc.containingPackage().name();
+      if (!pkg.startsWith("java.")) {
+        return;
+      }
+      String clazz = classDoc.name();
+
+      jreLink += pkg.replace('.', '/') + "/";
+      jreLink += clazz;
+      jreLink += ".html";
+    } else if (doc instanceof ExecutableMemberDoc) {
+      ExecutableMemberDoc execMemberDoc = (ExecutableMemberDoc) doc;
+      String pkg = enclosing.containingPackage().name();
+      if (!pkg.startsWith("java.")) {
+        return;
+      }
+      String clazz = enclosing.name();
+      String method = execMemberDoc.name();
+      String sig = execMemberDoc.signature();
+
+      jreLink += pkg.replace('.', '/') + "/";
+      jreLink += clazz;
+      jreLink += ".html";
+      jreLink += "#";
+      jreLink += method;
+      jreLink += sig;
+    } else if (doc instanceof PackageDoc) {
+      String pkg = doc.name();
+      if (!pkg.startsWith("java.")) {
+        return;
+      }
+      jreLink += pkg.replace('.', '/') + "/package-summary.html";
+    } else if (doc instanceof FieldDoc) {
+      FieldDoc fieldDoc = (FieldDoc) doc;
+      String pkg = enclosing.containingPackage().name();
+      if (!pkg.startsWith("java.")) {
+        return;
+      }
+      String clazz = fieldDoc.containingClass().name();
+      String field = fieldDoc.name();
+
+      jreLink += pkg.replace('.', '/') + "/";
+      jreLink += clazz;
+      jreLink += ".html";
+      jreLink += "#";
+      jreLink += field;
+    }
+
+    // Add the link.
+    //
+    beginln("jre");
+    text(jreLink);
+    endln();
+  }
+
+  private void emitLocation(Doc doc) {
+    Doc parent = getParentDoc(doc);
+    if (parent != null) {
+      beginln("location");
+      emitLocationLink(parent);
+      endln();
+    }
+  }
+
+  private void emitLocationLink(Doc doc) {
+    // Intentionally reverses the order.
+    //
+    String myId;
+    String myTitle;
+    if (doc instanceof MemberDoc) {
+      MemberDoc memberDoc = (MemberDoc) doc;
+      myId = getId(memberDoc);
+      myTitle = memberDoc.name();
+    } else if (doc instanceof ClassDoc) {
+      ClassDoc classDoc = (ClassDoc) doc;
+      myId = getId(classDoc);
+      myTitle = classDoc.name();
+    } else if (doc instanceof PackageDoc) {
+      PackageDoc pkgDoc = (PackageDoc) doc;
+      myId = getId(pkgDoc);
+      myTitle = pkgDoc.name();
+    } else if (doc instanceof RootDoc) {
+      myId = fRootDocId;
+      myTitle = fRootDoc.name();
+    } else {
+      throw new IllegalStateException(
+        "Expected only a member, type, or package");
+    }
+
+    Doc parent = getParentDoc(doc);
+    if (parent != null) {
+      emitLocationLink(parent);
+    }
+
+    beginln("link", "ref", myId);
+
+    Tag[] titleTag = doc.tags("@title");
+    if (titleTag.length > 0) {
+      myTitle = titleTag[0].text().trim();
+    }
+
+    if (myTitle == null || myTitle.length() == 0) {
+      myTitle = "[NO TITLE]";
+    }
+
+    text(myTitle);
+
+    endln();
+  }
+
+  private void emitModifiers(ProgramElementDoc doc) {
+    if (doc.isPrivate())
+      beginEndln("isPrivate");
+    else if (doc.isProtected())
+      beginEndln("isProtected");
+    else if (doc.isPublic())
+      beginEndln("isPublic");
+    else if (doc.isPackagePrivate())
+      beginEndln("isPackagePrivate");
+
+    if (doc.isStatic())
+      beginEndln("isStatic");
+
+    if (doc.isFinal())
+      beginEndln("isFinal");
+
+    if (doc instanceof MethodDoc) {
+      MethodDoc methodDoc = (MethodDoc) doc;
+
+      if (methodDoc.isAbstract())
+        beginEndln("isAbstract");
+
+      if (methodDoc.isSynchronized())
+        beginEndln("isSynchronized");
+    }
+  }
+
+  private void emitOutOfLineTags(Tag[] tags) {
+    beginln("tags");
+    processTags(tags);
+    endln();
+  }
+
+  private void emitType(Type type) {
+    ClassDoc typeAsClass = type.asClassDoc();
+
+    if (typeAsClass != null)
+      begin("type", "ref", getId(typeAsClass));
+    else
+      begin("type");
+
+    String typeName = type.typeName();
+    String dims = type.dimension();
+
+    text(typeName + dims);
+
+    end();
+  }
+
+  private void end() {
+    pw.print("</" + fTagStack.pop() + ">");
+  }
+
+  private void endCDATA() {
+    pw.print("]]>");
+  }
+
+  private void endln() {
+    end();
+    pw.println();
+  }
+
+  private MethodDoc findMatchingInterfaceMethodDoc(ClassDoc[] interfaces,
+      MethodDoc methodDoc) {
+    if (interfaces != null) {
+      // Look through the methods on superInterface for a matching methodDoc.
+      //
+      for (int intfIndex = 0; intfIndex < interfaces.length; ++intfIndex) {
+        ClassDoc currentIntfDoc = interfaces[intfIndex];
+        MethodDoc[] intfMethodDocs = currentIntfDoc.methods();
+        for (int methodIndex = 0; methodIndex < intfMethodDocs.length; ++methodIndex) {
+          MethodDoc intfMethodDoc = intfMethodDocs[methodIndex];
+          String methodDocName = methodDoc.name();
+          String intfMethodDocName = intfMethodDoc.name();
+          if (methodDocName.equals(intfMethodDocName)) {
+            if (methodDoc.signature().equals(intfMethodDoc.signature())) {
+              // It's a match!
+              //
+              return intfMethodDoc;
+            }
+          }
+        }
+
+        // Try the superinterfaces of this interface.
+        //
+        MethodDoc foundMethodDoc = findMatchingInterfaceMethodDoc(
+          currentIntfDoc.interfaces(), methodDoc);
+        if (foundMethodDoc != null)
+          return foundMethodDoc;
+      }
+    }
+
+    // Just didn't find it anywhere. Must not be based on an implemented
+    // interface.
+    //
+    return null;
+  }
+
+  private String getId(ClassDoc classDoc) {
+    return classDoc.qualifiedName();
+  }
+
+  private String getId(ExecutableMemberDoc memberDoc) {
+    // Use the mangled name to look up a unique id (based on its hashCode).
+    //
+    String clazz = memberDoc.containingClass().qualifiedName();
+    String id = clazz + "#" + memberDoc.name() + memberDoc.signature();
+    return id;
+  }
+
+  private String getId(FieldDoc fieldDoc) {
+    String clazz = fieldDoc.containingClass().qualifiedName();
+    String id = clazz + "#" + fieldDoc.name();
+    return id;
+  }
+
+  private String getId(MemberDoc memberDoc) {
+    if (memberDoc.isMethod())
+      return getId((MethodDoc) memberDoc);
+    else if (memberDoc.isConstructor())
+      return getId((ConstructorDoc) memberDoc);
+    else if (memberDoc.isField())
+      return getId((FieldDoc) memberDoc);
+    else
+      throw new RuntimeException("Unknown member type");
+  }
+
+  private String getId(PackageDoc packageDoc) {
+    return packageDoc.name();
+  }
+
+  private Doc getParentDoc(Doc doc) {
+    if (doc instanceof MemberDoc) {
+      MemberDoc memberDoc = (MemberDoc) doc;
+      return memberDoc.containingClass();
+    } else if (doc instanceof ClassDoc) {
+      ClassDoc classDoc = (ClassDoc) doc;
+      Doc enclosingClass = classDoc.containingClass();
+      if (enclosingClass != null) {
+        return enclosingClass;
+      } else {
+        return classDoc.containingPackage();
+      }
+    } else if (doc instanceof PackageDoc) {
+      return fRootDoc;
+    } else if (doc instanceof RootDoc) {
+      return null;
+    } else {
+      throw new IllegalStateException(
+        "Expected only a member, type, or package");
+    }
+  }
+
+  private boolean looksSynthesized(ExecutableMemberDoc memberDoc) {
+    SourcePosition memberPos = memberDoc.position();
+    int memberLine = memberPos.line();
+
+    SourcePosition classPos = memberDoc.containingClass().position();
+    int classLine = classPos.line();
+
+    if (memberLine == classLine)
+      return true;
+    else
+      return false;
+  }
+
+  private void process(ClassDoc enclosing, ClassDoc classDoc) {
+    // Make sure it isn't a @skip-ped topic.
+    //
+    if (classDoc.tags("@skip").length > 0) {
+      // This one is explicitly skipped right now.
+      //
+      return;
+    }
+
+    if (classDoc.isInterface())
+      beginln("interface");
+    else
+      beginln("class");
+
+    emitIdentity(getId(classDoc), classDoc.name());
+    emitLocation(classDoc);
+    emitDescription(enclosing, classDoc, classDoc.firstSentenceTags(),
+      classDoc.inlineTags());
+    emitOutOfLineTags(classDoc.tags());
+    emitModifiers(classDoc);
+
+    ClassDoc superclassDoc = classDoc.superclass();
+    if (superclassDoc != null) {
+      beginln("superclass", "ref", getId(superclassDoc));
+      text(superclassDoc.name());
+      endln();
+    }
+
+    ClassDoc[] superinterfacesDoc = classDoc.interfaces();
+    for (int i = 0; i < superinterfacesDoc.length; i++) {
+      ClassDoc superinterfaceDoc = superinterfacesDoc[i];
+      beginln("superinterface", "ref", getId(superinterfaceDoc));
+      text(superinterfaceDoc.name());
+      endln();
+    }
+
+    ClassDoc[] cda = classDoc.innerClasses();
+    for (int i = 0; i < cda.length; i++) {
+      process(classDoc, cda[i]);
+    }
+
+    FieldDoc[] fda = classDoc.fields();
+    for (int i = 0; i < fda.length; i++)
+      process(classDoc, fda[i]);
+
+    ConstructorDoc[] ctorDocs = classDoc.constructors();
+    for (int i = 0; i < ctorDocs.length; i++)
+      process(classDoc, ctorDocs[i]);
+
+    MethodDoc[] methods = classDoc.methods();
+    for (int i = 0; i < methods.length; i++)
+      process(classDoc, methods[i]);
+
+    endln();
+  }
+
+  private void process(ClassDoc enclosing, ExecutableMemberDoc memberDoc) {
+    if (looksSynthesized(memberDoc)) {
+      // Skip it.
+      //
+      return;
+    }
+
+    // Make sure it isn't a @skip-ped member.
+    //
+    if (memberDoc.tags("@skip").length > 0) {
+      // This one is explicitly skipped right now.
+      //
+      return;
+    }
+
+    if (memberDoc instanceof MethodDoc) {
+      beginln("method");
+      emitIdentity(getId(memberDoc), memberDoc.name());
+      emitLocation(memberDoc);
+
+      // If this method is not explicitly documented, use the best inherited
+      // one.
+      //
+      String rawComment = memberDoc.getRawCommentText();
+      if (rawComment.length() == 0) {
+        // Switch out the member doc being used.
+        //
+        MethodDoc methodDoc = (MethodDoc) memberDoc;
+        MethodDoc superMethodDoc = methodDoc.overriddenMethod();
+
+        if (superMethodDoc == null) {
+
+          ClassDoc classDocToTry = methodDoc.containingClass();
+          while (classDocToTry != null) {
+            // See if this is a method from an interface.
+            // If so, borrow its description.
+            //
+            superMethodDoc = findMatchingInterfaceMethodDoc(
+              classDocToTry.interfaces(), methodDoc);
+
+            if (superMethodDoc != null)
+              break;
+
+            classDocToTry = classDocToTry.superclass();
+          }
+        }
+
+        if (superMethodDoc != null) {
+          // Borrow the description from the superclass/superinterface.
+          //
+          memberDoc = superMethodDoc;
+        }
+      }
+    } else if (memberDoc instanceof ConstructorDoc) {
+      beginln("constructor");
+      emitIdentity(getId(memberDoc), memberDoc.containingClass().name());
+      emitLocation(memberDoc);
+    } else {
+      throw new IllegalStateException("What kind of executable member is this?");
+    }
+
+    emitDescription(enclosing, memberDoc, memberDoc.firstSentenceTags(),
+      memberDoc.inlineTags());
+    emitOutOfLineTags(memberDoc.tags());
+    emitModifiers(memberDoc);
+
+    begin("flatSignature");
+    text(memberDoc.flatSignature());
+    end();
+
+    // Return type if it's a method
+    //
+    if (memberDoc instanceof MethodDoc)
+      emitType(((MethodDoc) memberDoc).returnType());
+
+    // Parameters
+    //
+    beginln("params");
+    Parameter[] pda = memberDoc.parameters();
+    for (int i = 0; i < pda.length; i++) {
+      Parameter pd = pda[i];
+
+      begin("param");
+      emitType(pd.type());
+      begin("name");
+      text(pd.name());
+      end();
+      end();
+    }
+    endln();
+
+    // Exceptions thrown
+    //
+    ClassDoc[] tea = memberDoc.thrownExceptions();
+    if (tea.length > 0) {
+      beginln("throws");
+      for (int i = 0; i < tea.length; ++i) {
+        ClassDoc te = tea[i];
+        beginln("throw", "ref", getId(te));
+        text(te.name());
+        endln();
+      }
+      endln();
+    }
+
+    // Maybe show code
+    //
+    if (fShowCode) {
+      SourcePosition pos = memberDoc.position();
+      if (pos != null) {
+        beginln("code");
+        String source = slurpSource(pos);
+        begin("pre", "class", "code");
+        beginCDATA();
+        text(source);
+        endCDATA();
+        endln();
+        endln();
+      }
+    }
+
+    endln();
+  }
+
+  private void process(ClassDoc enclosing, FieldDoc fieldDoc) {
+    // Make sure it isn't @skip-ped.
+    //
+    if (fieldDoc.tags("@skip").length > 0) {
+      // This one is explicitly skipped right now.
+      //
+      return;
+    }
+
+    String commentText = fieldDoc.commentText();
+    if (fieldDoc.isPrivate()
+      && (commentText == null || commentText.length() == 0))
+      return;
+
+    beginln("field");
+    emitIdentity(fieldDoc.qualifiedName(), fieldDoc.name());
+    emitLocation(fieldDoc);
+    emitDescription(enclosing, fieldDoc, fieldDoc.firstSentenceTags(),
+      fieldDoc.inlineTags());
+    emitOutOfLineTags(fieldDoc.tags());
+    emitModifiers(fieldDoc);
+    emitType(fieldDoc.type());
+    endln();
+  }
+
+  private void process(PackageDoc packageDoc) {
+    beginln("package");
+
+    emitIdentity(packageDoc.name(), packageDoc.name());
+    emitLocation(packageDoc);
+    emitDescription(null, packageDoc, packageDoc.firstSentenceTags(),
+      packageDoc.inlineTags());
+    emitOutOfLineTags(packageDoc.tags());
+
+    // Top-level classes
+    //
+    ClassDoc[] cda = packageDoc.allClasses();
+    for (int i = 0; i < cda.length; i++) {
+      ClassDoc cd = cda[i];
+
+      // Make sure we have source.
+      //
+      SourcePosition p = cd.position();
+      if (p == null || p.line() == 0) {
+        // Skip this since it isn't ours (otherwise we would have source).
+        //
+        continue;
+      }
+
+      if (cd.containingClass() == null) {
+        process(cd, cda[i]);
+      } else {
+        // Not a top-level class.
+        //
+        cd = cda[i];
+      }
+    }
+
+    endln();
+  }
+
+  private void process(RootDoc rootDoc) {
+    try {
+      fRootDoc = rootDoc;
+      File outputFile = new File(fOutputPath);
+      outputFile.getParentFile().mkdirs();
+      FileWriter fw = new FileWriter(outputFile);
+      pw = new PrintWriter(fw, true);
+
+      beginln("booklet");
+
+      fRootDocId = "";
+      String title = "";
+      Tag[] idTags = rootDoc.tags("@id");
+      if (idTags.length > 0) {
+        fRootDocId = idTags[0].text();
+      } else {
+        fRootDoc.printWarning("Expecting @id in an overview html doc; see -overview");
+      }
+
+      Tag[] titleTags = rootDoc.tags("@title");
+      if (titleTags.length > 0) {
+        title = titleTags[0].text();
+      } else {
+        fRootDoc.printWarning("Expecting @title in an overview html doc; see -overview");
+      }
+
+      emitIdentity(fRootDocId, title);
+      emitLocation(rootDoc);
+      emitDescription(null, rootDoc, rootDoc.firstSentenceTags(),
+        rootDoc.inlineTags());
+      emitOutOfLineTags(rootDoc.tags());
+
+      // Create a list of the packages to iterate over.
+      //
+      HashSet packageNames = new HashSet();
+      ClassDoc[] cda = fRootDoc.classes();
+      for (int i = 0; i < cda.length; i++) {
+        ClassDoc cd = cda[i];
+        // Only top-level classes matter.
+        //
+        if (cd.containingClass() == null) {
+          packageNames.add(cd.containingPackage().name());
+        }
+      }
+
+      // Packages
+      //
+      for (Iterator iter = packageNames.iterator(); iter.hasNext();) {
+        String packageName = (String) iter.next();
+
+        // Only process this package if either no "docpkg" is set, or it is
+        // included.
+        //
+        if (fPackagesToGenerate == null
+          || fPackagesToGenerate.contains(packageName)) {
+          PackageDoc pd = fRootDoc.packageNamed(packageName);
+          process(pd);
+        }
+      }
+
+      endln();
+    } catch (Exception e) {
+      e.printStackTrace();
+      fRootDoc.printError("Caught exception: " + e.toString());
+    }
+  }
+
+  private void processSeeTag(SeeTag seeTag) {
+    String ref = null;
+    ClassDoc cd = null;
+    PackageDoc pd = null;
+    MemberDoc md = null;
+    String title = null;
+
+    // Ordered: most-specific to least-specific
+    if (null != (md = seeTag.referencedMember())) {
+      ref = getId(md);
+    } else if (null != (cd = seeTag.referencedClass())) {
+      ref = getId(cd);
+
+      // See if the target has a title.
+      //
+      Tag[] titleTag = cd.tags("@title");
+      if (titleTag.length > 0) {
+        title = titleTag[0].text().trim();
+        if (title.length() == 0)
+          title = null;
+      }
+    } else if (null != (pd = seeTag.referencedPackage())) {
+      ref = getId(pd);
+    }
+
+    String label = seeTag.label();
+
+    // If there is a label, use it.
+    if (label == null || (label != null && label.trim().length() == 0)) {
+
+      // If there isn't a label, see if the @see target has a @title.
+      //
+      if (title != null) {
+        label = title;
+      } else {
+        label = seeTag.text();
+
+        if (label.endsWith("."))
+          label = label.substring(0, label.length() - 1);
+
+        // Rip off all but the last interesting part to prevent fully-qualified
+        // names everywhere.
+        //
+        int last1 = label.lastIndexOf('.');
+        int last2 = label.lastIndexOf('#');
+
+        if (last2 > last1) {
+          // Use the class name plus the member name.
+          //
+          label = label.substring(last1 + 1).replace('#', '.');
+        } else if (last1 != -1)
+          label = label.substring(last1 + 1);
+
+        if (label.charAt(0) == '.') {
+          // Started with "#" so remove the dot.
+          //
+          label = label.substring(1);
+        }
+      }
+    }
+
+    if (ref != null) {
+      begin("link", "ref", ref);
+      text(label != null ? label.trim() : "");
+      end();
+    } else {
+      fRootDoc.printWarning(seeTag.position(),
+        "Unverifiable cross-reference to '" + seeTag.text() + "'");
+      // The link probably won't work, but emit it anyway.
+      begin("link");
+      text(label != null ? label.trim() : "");
+      end();
+    }
+  }
+
+  private void processTags(Tag[] tags) {
+    for (int i = 0; i < tags.length; i++) {
+      Tag tag = tags[i];
+      String tagKind = tag.kind();
+      if (tagKind.equals("Text")) {
+        text(tag.text());
+      } else if (tagKind.equals("@see")) {
+        processSeeTag((SeeTag) tag);
+      } else if (tagKind.equals("@param")) {
+        ParamTag paramTag = (ParamTag) tag;
+        beginln("param");
+        begin("name");
+        text(paramTag.parameterName());
+        end();
+        begin("description");
+        processTags(paramTag.inlineTags());
+        end();
+        endln();
+      } else if (tagKind.equals("@example")) {
+        ExtraClassResolver extraClassResolver = getExtraClassResolver(tag);
+        SourcePosition pos = LinkResolver.resolveLink(tag, extraClassResolver);
+        String source = slurpSource(pos);
+        begin("pre", "class", "code");
+        beginCDATA();
+        text(source);
+        endCDATA();
+        endln();
+      } else if (tagKind.equals("@gwt.include")) {
+        String contents = ResourceIncluder.getResourceFromClasspathScrubbedForHTML(tag);
+        begin("pre", "class", "code");
+        text(contents);
+        endln();
+      } else if (!fStandardTagKinds.contains(tag.name())) {
+        // Custom tag; pass it along other tag.
+        //
+        String tagName = tag.name().substring(1);
+        begin(tagName);
+        processTags(tag.inlineTags());
+        end();
+      }
+    }
+  }
+
+  private ExtraClassResolver getExtraClassResolver(Tag tag) {
+    
+    if (tag.holder() instanceof PackageDoc) {
+      return new ExtraClassResolver() {
+        public ClassDoc findClass(String className) {
+          return fRootDoc.classNamed(className);
+        }};
+    }
+    
+    return null;
+  }
+
+  private void text(String s) {
+    pw.print(s);
+  }
+
+  private String fOutputPath;
+  private HashSet fPackagesToGenerate;
+  private RootDoc fRootDoc;
+  private String fRootDocId;
+  private boolean fShowCode;
+  private HashSet fStandardTagKinds = new HashSet();
+  private Stack fTagStack = new Stack();
+  private PrintWriter pw;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/DocTool.java b/build-tools/doctool/src/com/google/doctool/DocTool.java
new file mode 100644
index 0000000..44b58e6
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/DocTool.java
@@ -0,0 +1,693 @@
+package com.google.doctool;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+public class DocTool {
+
+  public static final class GenType {
+    private GenType() {
+    }
+  }
+
+  private class ImageCopier extends DefaultHandler {
+
+    private ImageCopier(File htmlDir) {
+      fHtmlDir = htmlDir;
+    }
+
+    public void startElement(String uri, String localName, String qName,
+        Attributes attributes) throws SAXException {
+      if (qName.equalsIgnoreCase("img")) {
+        String imgSrc = attributes.getValue("src");
+        if (imgSrc != null) {
+          boolean found = false;
+          for (int i = 0, n = fImagePath.length; i < n; ++i) {
+            File dir = fImagePath[i];
+            File inFile = new File(dir, imgSrc);
+            if (inFile.exists()) {
+              // Copy it over.
+              //
+              found = true;
+              File outFile = new File(fHtmlDir, imgSrc);
+
+              if (outFile.exists()) {
+                if (outFile.lastModified() > inFile.lastModified()) {
+                  // Already up to date.
+                  break;
+                }
+              } else {
+                File outFileDir = outFile.getParentFile();
+                if (!outFileDir.exists() && !outFileDir.mkdirs()) {
+                  fErr.println("Unable to create image output dir "
+                    + outFileDir);
+                  break;
+                }
+              }
+              if (!copyFile(inFile, outFile)) {
+                fErr.println("Unable to copy image file " + outFile);
+              }
+            }
+          }
+          if (!found) {
+            fErr.println("Unable to find image " + imgSrc);
+          }
+        }
+      }
+    }
+
+    private final File fHtmlDir;
+  }
+
+  private static final Pattern IN_XML_FILENAME = Pattern.compile(
+    "(.+)\\.([^\\.]+)\\.xml", Pattern.CASE_INSENSITIVE);
+
+  public static void main(String[] args) {
+    DocToolFactory factory = new DocToolFactory();
+    String arg;
+    String pathSep = System.getProperty("path.separator");
+    for (int i = 0, n = args.length; i < n; ++i) {
+      if (tryParseFlag(args, i, "-help")) {
+        printHelp();
+        return;
+      } else if (null != (arg = tryParseArg(args, i, "-out"))) {
+        ++i;
+        factory.setOutDir(arg);
+      } else if (null != (arg = tryParseArg(args, i, "-html"))) {
+        ++i;
+        factory.setGenerateHtml(true);
+        factory.setTitle(arg);
+
+        // Slurp every arg not prefixed with "-".
+        for (; i + 1 < n && !args[i + 1].startsWith("-"); ++i) {
+          factory.addHtmlFileBase(args[i + 1]);
+        }
+      } else if (null != (arg = tryParseArg(args, i, "-overview"))) {
+        ++i;
+        factory.setOverviewFile(arg);
+      } else if (null != (arg = tryParseArg(args, i, "-sourcepath"))) {
+        ++i;
+        String[] entries = arg.split("\\" + pathSep);
+        for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+          factory.addToSourcePath(entries[entryIndex]);
+        }
+      } else if (null != (arg = tryParseArg(args, i, "-classpath"))) {
+        ++i;
+        String[] entries = arg.split("\\" + pathSep);
+        for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+          factory.addToClassPath(entries[entryIndex]);
+        }
+      } else if (null != (arg = tryParseArg(args, i, "-packages"))) {
+        ++i;
+        String[] entries = arg.split("\\" + pathSep);
+        for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+          factory.addToPackages(entries[entryIndex]);
+        }
+      } else if (null != (arg = tryParseArg(args, i, "-imagepath"))) {
+        ++i;
+        String[] entries = arg.split("\\" + pathSep);
+        for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+          factory.addToImagePath(entries[entryIndex]);
+        }
+      } else {
+        if (factory.getFileType() == null)
+          factory.setFileType(args[i]);
+        else
+          factory.setFileBase(args[i]);
+      }
+    }
+
+    DocTool docTool = factory.create(System.out, System.err);
+    if (docTool != null)
+      docTool.process();
+  }
+
+  public static boolean recursiveDelete(File file) {
+    if (file.isDirectory()) {
+      File[] children = file.listFiles();
+      if (children != null) {
+        for (int i = 0; i < children.length; i++) {
+          if (!recursiveDelete(children[i]))
+            return false;
+        }
+      }
+    }
+    if (!file.delete()) {
+      System.err.println("Unable to delete " + file.getAbsolutePath());
+      return false;
+    }
+    return true;
+  }
+
+  private static void printHelp() {
+    String s = "";
+    s += "DocTool (filetype filebase)? [docset-creation-options] [html-creation-options]\n";
+    s += "    Creates structured javadoc xml output from Java source and/or\n";
+    s += "    a table of contents and a set of cross-linked html files.\n";
+    s += "    Specifying filebase/filetype produces output file \"filebase.filetype.xml\".\n";
+    s += "    Specifying -html produces output files in ${out}/html.\n";
+    s += "\n";
+    s += "[docset-creation-options] are\n";
+    s += "  -out\n";
+    s += "    The output directory\n";
+    s += "  -overview\n";
+    s += "    The overview html file for this doc set\n";
+    s += "  -sourcepath path\n";
+    s += "    The path to find Java source for this doc set\n";
+    s += "  -classpath path\n";
+    s += "    The path to find imported classes for this doc set\n";
+    s += "  -packages package-names\n";
+    s += "    The command-separated list of package names to include in this doc set\n";
+    s += "\n";
+    s += "[html-creation-options] are\n";
+    s += "  -html title filebase+\n";
+    s += "    Causes topics in the named filebase(s) to be merged and converted into html\n";
+    s += "  -imagepath\n";
+    s += "    The semicolon-separated path to find images for html\n";
+    System.out.println(s);
+  }
+
+  /**
+   * Parse a flag with a argument.
+   */
+  private static String tryParseArg(String[] args, int i, String name) {
+    if (i < args.length) {
+      if (args[i].equals(name)) {
+        if (i + 1 < args.length) {
+          String arg = args[i + 1];
+          if (arg.startsWith("-")) {
+            System.out.println("Warning: arg to " + name
+              + " looks more like a flag: " + arg);
+          }
+          return arg;
+        } else {
+          throw new IllegalArgumentException("Expecting an argument after "
+            + name);
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Parse just a flag with no subsequent argument.
+   */
+  private static boolean tryParseFlag(String[] args, int i, String name) {
+    if (i < args.length) {
+      if (args[i].equals(name))
+        return true;
+    }
+    return false;
+  }
+
+  DocTool(PrintStream out, PrintStream err, File outDir, boolean generateHtml,
+      String title, String[] htmlFileBases, String fileType, String fileBase,
+      File overviewFile, File[] sourcePath, File[] classPath,
+      String[] packages, File[] imagePath) {
+    fOut = out;
+    fErr = err;
+    fOutDir = outDir;
+    fGenerateHtml = generateHtml;
+    fFileBase = fileBase;
+    fFileType = fileType;
+    fOverviewFile = overviewFile;
+    fSourcePath = sourcePath;
+    fClassPath = classPath;
+    fPackages = packages;
+    fImagePath = imagePath;
+    fTitle = title;
+    fHtmlFileBases = (String[]) htmlFileBases.clone();
+  }
+
+  public boolean copyFile(File in, File out) {
+    FileInputStream fis = null;
+    FileOutputStream fos = null;
+    try {
+      fis = new FileInputStream(in);
+      fos = new FileOutputStream(out);
+      byte[] buf = new byte[4096];
+      int i = 0;
+      while ((i = fis.read(buf)) != -1) {
+        fos.write(buf, 0, i);
+      }
+      return true;
+    } catch (Exception e) {
+      return false;
+    } finally {
+      close(fis);
+      close(fos);
+    }
+  }
+
+  private void close(InputStream is) {
+    if (is != null) {
+      try {
+        is.close();
+      } catch (IOException e) {
+        e.printStackTrace(fErr);
+      }
+    }
+  }
+
+  private void close(OutputStream os) {
+    if (os != null) {
+      try {
+        os.close();
+      } catch (IOException e) {
+        e.printStackTrace(fErr);
+      }
+    }
+  }
+
+  private boolean copyImages(File htmlDir, File mergedTopicsFile) {
+    FileReader fileReader = null;
+    Throwable caught = null;
+    try {
+      fileReader = new FileReader(mergedTopicsFile);
+      SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+      InputSource inputSource = new InputSource(fileReader);
+      XMLReader xmlReader = parser.getXMLReader();
+      xmlReader.setContentHandler(new ImageCopier(htmlDir));
+      xmlReader.parse(inputSource);
+      return true;
+    } catch (SAXException e) {
+      caught = e;
+      Exception inner = e.getException();
+      if (inner != null)
+        caught = inner;
+    } catch (ParserConfigurationException e) {
+      caught = e;
+    } catch (IOException e) {
+      caught = e;
+    } finally {
+      try {
+        if (fileReader != null)
+          fileReader.close();
+      } catch (IOException e) {
+        e.printStackTrace(fErr);
+      }
+    }
+    caught.printStackTrace(fErr);
+    return false;
+  }
+
+  private Set findSourcePackages() {
+    Set results = new HashSet();
+    for (int i = 0, n = fSourcePath.length; i < n; ++i) {
+      File srcDir = fSourcePath[i];
+      findSourcePackages(results, srcDir, "");
+    }
+    return results;
+  }
+
+  private void findSourcePackages(Set results, File dir, String parentPackage) {
+    File[] children = dir.listFiles();
+    if (children != null) {
+      for (int i = 0, n = children.length; i < n; ++i) {
+        File child = children[i];
+        String childName = parentPackage
+          + (parentPackage.length() > 0 ? "." : "") + child.getName();
+        if (child.isDirectory()) {
+          // Recurse
+          findSourcePackages(results, child, childName);
+        } else if (child.getName().endsWith(".java")) {
+          // Only include this dir as a result if there's at least one java file
+          results.add(parentPackage);
+        }
+      }
+    }
+  }
+
+  private String flattenPath(File[] entries) {
+    String pathSep = System.getProperty("path.separator");
+    String path = "";
+    for (int i = 0, n = entries.length; i < n; ++i) {
+      File entry = entries[i];
+      if (i > 0)
+        path += pathSep;
+      path += entry.getAbsolutePath();
+    }
+    return path;
+  }
+
+  private void freshenIf(File file) {
+    if (!file.isFile())
+      return;
+
+    String name = file.getName();
+    Matcher matcher = IN_XML_FILENAME.matcher(name);
+    if (matcher.matches()) {
+      String suffix = "." + matcher.group(2) + ".xml";
+      File topicFile = tryReplaceSuffix(file, suffix, ".topics.xml");
+      if (topicFile != null) {
+        if (file.lastModified() > topicFile.lastModified()) {
+          String xsltFileName = matcher.group(2) + "-" + "topics.xslt";
+          String xslt = getFileFromClassPath(xsltFileName); // yucky slow
+          fOut.println(file + " -> " + topicFile);
+          transform(xslt, file, topicFile, null);
+        }
+      }
+    }
+  }
+
+  private boolean genHtml() {
+    // Make sure the html directory exists.
+    //
+    File htmlDir = new File(fOutDir, "html");
+    if (!htmlDir.exists() && !htmlDir.mkdirs()) {
+      fErr.println("Cannot create html output directory "
+        + htmlDir.getAbsolutePath());
+      return false;
+    }
+
+    // Merge all *.topics.xml into one topics.xml file.
+    //
+    File mergedTopicsFile = new File(fOutDir, "topics.xml");
+    if (!mergeTopics(mergedTopicsFile))
+      return false;
+
+    // Parse it all to find the images and copy them over.
+    //
+    copyImages(htmlDir, mergedTopicsFile);
+
+    // Transform to merged topics into merged htmls.
+    //
+    File mergedHtmlsFile = new File(htmlDir, "topics.htmls");
+    long lastModifiedHtmls = mergedHtmlsFile.lastModified();
+    long lastModifiedTopics = mergedTopicsFile.lastModified();
+    if (!mergedHtmlsFile.exists() || lastModifiedHtmls < lastModifiedTopics) {
+      String xsltHtmls = getFileFromClassPath("topics-htmls.xslt");
+
+      Map params = new HashMap();
+      params.put("title", fTitle);
+
+      transform(xsltHtmls, mergedTopicsFile, mergedHtmlsFile, params);
+
+      // Split the merged htmls into many html files.
+      //
+      if (!splitHtmls(mergedHtmlsFile))
+        return false;
+
+      // Create a table of contents.
+      //
+      File tocFile = new File(htmlDir, "contents.html");
+      String xsltToc = getFileFromClassPath("topics-toc.xslt");
+      transform(xsltToc, mergedTopicsFile, tocFile, params);
+
+      // Copy the CSS file over.
+      //
+      String css = getFileFromClassPath("doc.css");
+      try {
+        FileWriter cssWriter = new FileWriter(new File(htmlDir, "doc.css"));
+        cssWriter.write(css);
+        cssWriter.close();
+      } catch (IOException e) {
+        e.printStackTrace(fErr);
+      }
+    } else {
+      fOut
+        .println("Skipping html creation since nothing seems to have changed since "
+          + mergedHtmlsFile.getAbsolutePath());
+    }
+
+    return true;
+  }
+
+  private String getFileFromClassPath(String filename) {
+    InputStream in = null;
+    try {
+      in = getClass().getClassLoader().getResourceAsStream(filename);
+      try {
+        if (in == null) {
+          fErr.println("Cannot find file: " + filename);
+          System.exit(-1); // yuck
+        }
+        StringWriter sw = new StringWriter();
+        int ch;
+        while ((ch = in.read()) != -1) {
+          sw.write(ch);
+        }
+        return sw.toString();
+      } finally {
+        if (in != null)
+          in.close();
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private boolean mergeTopics(File mergedTopicsFile) {
+    try {
+      List args = new ArrayList();
+      args.add("join"); // what to do
+      args.add("topics"); // the outer element is <topics>
+      args.add(mergedTopicsFile.getAbsolutePath());
+
+      // For each of the htmlFileBases, try to find a file having that name to
+      // merge into the big topics doc.
+      //
+      boolean foundAny = false;
+      for (int i = 0, n = fHtmlFileBases.length; i < n; ++i) {
+        String filebase = fHtmlFileBases[i];
+        File fileToMerge = new File(fOutDir, filebase + ".topics.xml");
+        if (fileToMerge.exists()) {
+          foundAny = true;
+          args.add(fileToMerge.getAbsolutePath());
+        } else {
+          fErr.println("Unable to find " + fileToMerge.getName());
+        }
+      }
+
+      if (foundAny) {
+        String[] argArray = (String[]) args.toArray(new String[0]);
+        traceCommand("SplitterJoiner", argArray);
+        SplitterJoiner.main(argArray);
+      } else {
+        fErr.println("No topics found");
+        return false;
+      }
+    } catch (IOException e) {
+      e.printStackTrace(fErr);
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Runs the help process.
+   */
+  private boolean process() {
+    if (fFileType != null) {
+      // Produce XML from JavaDoc.
+      //
+      String fileName = fFileBase + "." + fFileType + ".xml";
+      if (!runBooklet(new File(fOutDir, fileName)))
+        return false;
+
+    }
+
+    // Process existing files to get them into topics format.
+    // Done afterwards for convenience when debugging your doc.
+    //
+    transformExistingIntoTopicXml();
+
+    if (fGenerateHtml) {
+      // Merge into HTML.
+      if (!genHtml())
+        return false;
+    }
+
+    return true;
+  }
+
+  private boolean runBooklet(File bkoutFile) {
+    // Write out the list of packages that can be found on the source path.
+    fOut.println("Creating " + bkoutFile.getAbsolutePath());
+    Set srcPackages = findSourcePackages();
+    if (srcPackages.isEmpty()) {
+      fErr.println("No input files found");
+      return false;
+    }
+
+    List args = new ArrayList();
+
+    // For now, harded-coded, but could be passed through
+    args.add("-source");
+    args.add("1.4");
+
+    // The doclet
+    args.add("-doclet");
+    args.add(Booklet.class.getName());
+
+    // Class path
+    args.add("-classpath");
+    args.add(flattenPath(fClassPath));
+
+    // Source path
+    args.add("-sourcepath");
+    args.add(flattenPath(fSourcePath));
+
+    // Overview file
+    if (fOverviewFile != null) {
+      args.add("-overview");
+      args.add(fOverviewFile.getAbsolutePath());
+    }
+
+    // Output file
+    args.add("-bkout");
+    args.add(bkoutFile.getAbsolutePath());
+
+    if (fPackages != null) {
+      // Specify the packages to actually emit doc for
+      StringBuffer bkdocpkg = new StringBuffer();
+      for (int i = 0; i < fPackages.length; i++) {
+        String pkg = fPackages[i];
+        bkdocpkg.append(pkg);
+        bkdocpkg.append(";");
+      }
+      args.add("-bkdocpkg");
+      args.add(bkdocpkg.toString());
+    }
+
+    args.add("-breakiterator");
+    
+    // Specify the set of input packages (needed by JavaDoc)
+    args.addAll(srcPackages);
+
+    String[] argArray = (String[]) args.toArray(new String[0]);
+    traceCommand("Booklet", argArray);
+    Booklet.main(argArray);
+
+    return bkoutFile.exists();
+  }
+
+  private boolean splitHtmls(File mergedHtmlsFile) {
+    try {
+      List args = new ArrayList();
+      args.add("split"); // what to do
+      args.add(mergedHtmlsFile.getAbsolutePath());
+      String[] argArray = (String[]) args.toArray(new String[0]);
+      traceCommand("SplitterJoiner", argArray);
+      SplitterJoiner.main(argArray);
+    } catch (IOException e) {
+      e.printStackTrace(fErr);
+      return false;
+    }
+    return true;
+  }
+
+  private void traceCommand(String cmd, String[] args) {
+    fOut.print(cmd);
+    for (int i = 0, n = args.length; i < n; ++i) {
+      String arg = args[i];
+      fOut.print(" ");
+      fOut.print(arg);
+    }
+    fOut.println();
+  }
+
+  private void transform(String xslt, File inFile, File outFile, Map params) {
+    Throwable caught = null;
+    try {
+      TransformerFactory transformerFactory = TransformerFactory.newInstance();
+      StreamSource xsltSource = new StreamSource(new StringReader(xslt));
+      Transformer transformer = transformerFactory.newTransformer(xsltSource);
+      transformer.setOutputProperty(
+        javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
+      transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT,
+        "yes");
+      transformer.setOutputProperty(
+        "{http://xml.apache.org/xslt}indent-amount", "4");
+
+      if (params != null) {
+        for (Iterator iter = params.entrySet().iterator(); iter.hasNext();) {
+          Map.Entry entry = (Map.Entry) iter.next();
+          transformer.setParameter((String) entry.getKey(), entry.getValue());
+        }
+      }
+
+      FileOutputStream fos = new FileOutputStream(outFile);
+      StreamResult result = new StreamResult(fos);
+      StreamSource xmlSource = new StreamSource(new FileReader(inFile));
+      transformer.transform(xmlSource, result);
+      fos.close();
+      return;
+    } catch (TransformerConfigurationException e) {
+      caught = e;
+    } catch (TransformerException e) {
+      caught = e;
+    } catch (IOException e) {
+      caught = e;
+    }
+    throw new RuntimeException("Unable to complete the xslt tranform", caught);
+  }
+
+  private void transformExistingIntoTopicXml() {
+    File[] children = fOutDir.listFiles();
+    if (children != null) {
+      for (int i = 0, n = children.length; i < n; ++i) {
+        File file = children[i];
+        freshenIf(file);
+      }
+    }
+  }
+
+  private File tryReplaceSuffix(File file, String oldSuffix, String newSuffix) {
+    String name = file.getName();
+    if (name.endsWith(oldSuffix)) {
+      String baseName = name.substring(0, name.length() - oldSuffix.length());
+      return new File(file.getParent(), baseName + newSuffix);
+    } else {
+      return null;
+    }
+  }
+
+  private final File[] fClassPath;
+  private final String[] fPackages;
+  private final PrintStream fErr;
+  private final String fFileBase;
+  private final String fFileType;
+  private final boolean fGenerateHtml;
+  private final String[] fHtmlFileBases;
+  private final File[] fImagePath;
+  private final PrintStream fOut;
+  private final File fOutDir;
+  private final File fOverviewFile;
+  private final File[] fSourcePath;
+  private final String fTitle;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/DocToolFactory.java b/build-tools/doctool/src/com/google/doctool/DocToolFactory.java
new file mode 100644
index 0000000..7cc51f9
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/DocToolFactory.java
@@ -0,0 +1,135 @@
+package com.google.doctool;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DocToolFactory {
+
+  public DocToolFactory() {
+  }
+
+  public void addHtmlFileBase(String filebase) {
+    htmlFileBases.add(filebase);
+  }
+
+  public void addToClassPath(String path) {
+    classPathEntries.add(new File(path));
+  }
+
+  public void addToImagePath(String path) {
+    imagePathEntries.add(new File(path));
+  }
+
+  public void addToPackages(String packageName) {
+    this.packageNameEntries.add(packageName);
+  }
+
+  public void addToSourcePath(String path) {
+    srcPathEntries.add(new File(path));
+  }
+
+  public DocTool create(PrintStream out, PrintStream err) {
+    File localOutDir = outDir;
+    if (localOutDir == null) {
+      localOutDir = new File(System.getProperty("user.dir"), "out");
+      out.println("Using default output directory: "
+        + localOutDir.getAbsolutePath());
+    }
+
+    File[] classPath = null;
+    File[] sourcePath = null;
+    String[] packageNames = null;
+    if (fileType != null) {
+      // Generating a doc set implies other settings.
+      //
+      if (fileBase == null) {
+        err.println("A file base must be specified when generating doc");
+        return null;
+      }
+//      if (overviewFile == null) {
+//        err
+//          .println("An overview file must be specified when generating doc; if you don't have one, use this:");
+//        err.println("<html><body>");
+//        err.println(" " + fileBase + "documentation");
+//        err.println(" @id " + fileBase + "-doc");
+//        err.println(" @title Documentation for " + fileBase);
+//        err.println("</body></html>");
+//        return null;
+//      }
+      classPath = (File[]) classPathEntries.toArray(new File[0]);
+      sourcePath = (File[]) srcPathEntries.toArray(new File[0]);
+      packageNames = (String[]) packageNameEntries.toArray(new String[0]);
+    }
+
+    if (generateHtml) {
+      if (title == null) {
+        out.println("A title must be specified when generating html");
+        return null;
+      }
+
+      if (htmlFileBases.isEmpty()) {
+        out.println("No html filebases were specified");
+        return null;
+      }
+    }
+
+    String[] htmlFileBaseArray = (String[]) htmlFileBases
+      .toArray(new String[0]);
+
+    // Handle -imagepath
+    //
+    List localImagePathEntries = new ArrayList(imagePathEntries);
+    if (localImagePathEntries.isEmpty()) {
+      out.println("No image path specified; using only the output dir");
+    }
+
+    localImagePathEntries.add(localOutDir);
+    File[] imagePath = (File[]) imagePathEntries.toArray(new File[0]);
+
+    return new DocTool(out, err, localOutDir, generateHtml, title,
+      htmlFileBaseArray, fileType, fileBase, overviewFile, sourcePath,
+      classPath, packageNames, imagePath);
+  }
+
+  public String getFileType() {
+    return fileType;
+  }
+
+  public void setFileBase(String fileBase) {
+    this.fileBase = fileBase;
+  }
+
+  public void setFileType(String fileType) {
+    this.fileType = fileType;
+  }
+
+  public void setGenerateHtml(boolean generateHtml) {
+    this.generateHtml = generateHtml;
+  }
+
+  public void setOutDir(String outDirPath) {
+    this.outDir = new File(outDirPath);
+  }
+
+  public void setOverviewFile(String overviewFile) {
+    this.overviewFile = new File(overviewFile);
+  }
+
+  public void setTitle(String title) {
+    this.title = title;
+  }
+
+  private final List classPathEntries = new ArrayList();
+  private String fileBase;
+  private String fileType;
+  private boolean generateHtml;
+  private final List htmlFileBases = new ArrayList();
+  private final List imagePathEntries = new ArrayList();
+  private File outDir;
+  private File overviewFile;
+  private final List packageNameEntries = new ArrayList();
+  private final List srcPathEntries = new ArrayList();
+  private String title;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/LinkResolver.java b/build-tools/doctool/src/com/google/doctool/LinkResolver.java
new file mode 100644
index 0000000..c36d2c0
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/LinkResolver.java
@@ -0,0 +1,123 @@
+package com.google.doctool;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.Doc;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.PackageDoc;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+
+public class LinkResolver {
+
+  public interface ExtraClassResolver {
+    ClassDoc findClass(String className);
+  }
+
+  public static SourcePosition resolveLink(Tag tag) {
+    return resolveLink(tag, null);
+  }
+
+  public static SourcePosition resolveLink(Tag tag,
+      ExtraClassResolver classResolver) {
+    String linkText = tag.text();
+    String className;
+    String methodSig;
+    int pos = linkText.indexOf('#');
+    if (pos >= 0) {
+      className = linkText.substring(0, pos);
+      methodSig = linkText.substring(pos + 1);
+    } else {
+      className = linkText;
+      methodSig = null;
+    }
+
+    ClassDoc containingClass = null;
+    Doc holder = tag.holder();
+    if (holder instanceof ClassDoc) {
+      containingClass = (ClassDoc) holder;
+    } else if (holder instanceof MemberDoc) {
+      containingClass = ((MemberDoc) holder).containingClass();
+    }
+
+    ClassDoc targetClass = null;
+    if (className.length() == 0) {
+      targetClass = containingClass;
+    } else if (holder instanceof PackageDoc) {
+      targetClass = ((PackageDoc) holder).findClass(className);
+    } else {
+      targetClass = containingClass.findClass(className);
+    }
+
+    if (targetClass == null) {
+      if (classResolver != null) {
+        targetClass = classResolver.findClass(className);
+      }
+      if (targetClass == null) {
+        System.err.println(tag.position().toString()
+          + ": unable to resolve class " + className + " for " + tag);
+        System.exit(1);
+      }
+    }
+
+    if (methodSig == null) {
+      return targetClass.position();
+    }
+
+    String paramSig = methodSig.substring(methodSig.indexOf('(') + 1,
+      methodSig.lastIndexOf(')'));
+    String[] resolvedParamTypes;
+    if (paramSig.length() > 0) {
+      String[] unresolvedParamTypes = paramSig.split("\\s*,\\s*");
+      resolvedParamTypes = new String[unresolvedParamTypes.length];
+      for (int i = 0; i < unresolvedParamTypes.length; ++i) {
+        ClassDoc paramType = containingClass.findClass(unresolvedParamTypes[i]);
+        if (paramType == null && classResolver != null) {
+          paramType = classResolver.findClass(unresolvedParamTypes[i]);
+        }
+        if (paramType == null) {
+          System.err.println(tag.position().toString()
+            + ": unable to resolve class " + unresolvedParamTypes[i] + " for "
+            + tag);
+          System.exit(1);
+        }
+        resolvedParamTypes[i] = paramType.qualifiedTypeName();
+      }
+    } else {
+      resolvedParamTypes = new String[0];
+    }
+
+    String possibleOverloads = "";
+
+    MethodDoc[] methods = targetClass.methods();
+    outer : for (int i = 0; i < methods.length; ++i) {
+      MethodDoc methodDoc = methods[i];
+      if (methodSig.startsWith(methodDoc.name())) {
+        possibleOverloads += "\n" + methodDoc.flatSignature();
+        Parameter[] tryParams = methodDoc.parameters();
+        if (resolvedParamTypes.length != tryParams.length) {
+          // param count mismatch
+          continue outer;
+        }
+        for (int j = 0; j < tryParams.length; ++j) {
+          if (!tryParams[j].type().qualifiedTypeName().equals(
+            resolvedParamTypes[j])) {
+            // param type mismatch
+            continue outer;
+          }
+        }
+        return methodDoc.position();
+      }
+    }
+
+    System.err.println(tag.position().toString()
+      + ": unable to resolve method for " + tag);
+    if (possibleOverloads.length() > 0) {
+      System.err.println("Possible overloads:" + possibleOverloads);
+    }
+    System.exit(1);
+    return null;
+  }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java b/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java
new file mode 100644
index 0000000..d3afc89
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java
@@ -0,0 +1,90 @@
+package com.google.doctool;
+
+import com.sun.javadoc.Tag;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ResourceIncluder {
+
+  public static String getResourceFromClasspathScrubbedForHTML(Tag tag) {
+    String partialPath = tag.text();
+    try {
+      String contents;
+      contents = getFileFromClassPath(partialPath);
+      contents = scrubForHtml(contents);
+      return contents;
+    } catch (IOException e) {
+      System.err.println(tag.position().toString()
+        + ": unable to include resource " + partialPath + " for tag " + tag);
+      System.exit(1);
+      return null;
+    }
+  }
+
+  /**
+   * Copied from {@link com.google.gwt.util.tools.Utility#close(InputStream)}.
+   */
+  public static void close(InputStream is) {
+    try {
+      if (is != null)
+        is.close();
+    } catch (IOException e) {
+    }
+  }
+
+  /**
+   * Copied from
+   * {@link com.google.gwt.util.tools.Utility#getFileFromClassPath(String)}.
+   */
+  private static String getFileFromClassPath(String partialPath)
+      throws IOException {
+    InputStream in = ResourceIncluder.class.getClassLoader().getResourceAsStream(
+      partialPath);
+    try {
+      if (in == null) {
+        throw new FileNotFoundException(partialPath);
+      }
+      ByteArrayOutputStream os = new ByteArrayOutputStream();
+      int ch;
+      while ((ch = in.read()) != -1) {
+        os.write(ch);
+      }
+      return new String(os.toByteArray(), "UTF-8");
+    } finally {
+      close(in);
+    }
+  }
+
+  private static String scrubForHtml(String contents) {
+    char[] chars = contents.toCharArray();
+    int len = chars.length;
+    StringBuffer sb = new StringBuffer(len);
+    for (int i = 0; i < len; ++i) {
+      char c = chars[i];
+      switch (c) {
+        case '\r':
+          // collapse \r\n into \n
+          if (i == len - 1 || chars[i + 1] != '\n') {
+            sb.append('\n');
+          }
+          break;
+        case '&':
+          sb.append("&amp;");
+          break;
+        case '<':
+          sb.append("&lt;");
+          break;
+        case '>':
+          sb.append("&gt;");
+          break;
+        default:
+          sb.append(c);
+          break;
+      }
+    }
+    return sb.toString();
+  }
+}
diff --git a/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java b/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java
new file mode 100644
index 0000000..473f830
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java
@@ -0,0 +1,273 @@
+package com.google.doctool;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * Takes an input stream and splits it into multiple files.
+ * A new file begins when a line in the input stream begins with a specific <emph>prefix</emph> 
+ * followed by whitespace then an absolute or relative file name to create.
+ */
+public class SplitterJoiner {
+
+  private static void split(String[] files) throws IOException {
+    BufferedReader reader = null;
+    String prefix = null;
+    File inputFile = null;
+
+    for (int i = 0; i < files.length; i++) {
+
+      // Close the current reader, if any.
+      //
+      if (reader != null)
+        reader.close();
+
+      // Open the next reader.
+      //                
+      String file = files[i];
+      inputFile = new File(file);
+      if (!inputFile.exists()) {
+        System.err.println("Error: Cannot find input file "
+          + inputFile.getPath());
+        return;
+      }
+      reader = new BufferedReader(new FileReader(inputFile));
+
+      // Parse the input
+      //
+      File outFile = null;
+      PrintWriter writer = null;
+      String line = reader.readLine();
+      while (line != null) {
+        if (prefix == null) {
+          // Learn the prefix.
+          //
+          prefix = line.trim();
+          if (prefix.length() == 0) {
+            // The first line with anything on it counts as the prefix.
+            // 
+            prefix = null;
+          }
+        } else if (line.startsWith(prefix)) {
+          // Close the current writer.
+          //
+          if (writer != null)
+            writer.close();
+
+          // Create the next writer.
+          //
+          String outPath = line.substring(prefix.length()).trim();
+          outFile = new File(outPath);
+          if (!outFile.isAbsolute() && inputFile != null) {
+            // Make the created file relative to the input file.
+            //
+            File absoluteParentDir = inputFile.getCanonicalFile()
+              .getParentFile();
+            outFile = new File(absoluteParentDir, outPath);
+            outFile.getParentFile().mkdirs();
+          }
+
+          writer = new PrintWriter(new FileWriter(outFile), true);
+          
+          writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
+          
+        } else if (writer != null) {
+          // Write this line to the current file.
+          //
+          writer.println(line);
+        } else {
+          // Ignored -- haven't yet seen a starting prefix.
+          //
+        }
+
+        line = reader.readLine();
+      }
+
+      if (writer != null)
+        writer.close();
+    }
+  }
+
+  private static void writeTopLevelChildren(Document doc, PrintWriter out)
+      throws TransformerException {
+    StreamResult result = new StreamResult(out);
+    TransformerFactory transformerFactory = TransformerFactory.newInstance();
+    Transformer transformer = transformerFactory.newTransformer();
+    transformer.setOutputProperty(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
+    transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
+    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
+      "4");
+
+    Node child = doc.getDocumentElement().getFirstChild();
+    while (child != null) {
+      DOMSource domSource = new DOMSource(child);
+      transformer.transform(domSource, result);
+      child = child.getNextSibling();
+    }
+  }
+
+  private static void emitFile(PrintWriter out, File outputFile, File inputFile)
+      throws IOException, ParserConfigurationException, SAXException,
+      TransformerException {
+    if (!inputFile.exists()) {
+      System.err
+        .println("Error: Cannot find input file " + inputFile.getPath());
+      return;
+    }
+
+    if (inputFile.getCanonicalFile().equals(outputFile)) {
+      // skip 
+      return;
+    }
+
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    Document doc = builder.parse(inputFile);
+    writeTopLevelChildren(doc, out);
+  }
+
+  private static void merge(String tag, String outputPath, String files[]) {
+    File outputFile = null;
+    try {
+      outputFile = new File(outputPath).getCanonicalFile();
+    } catch (IOException e) {
+      e.printStackTrace();
+      return;
+    }
+
+    // Maybe we don't need to do anything.
+    //
+    boolean skipMerge = true;
+    if (!outputFile.exists()) {
+      skipMerge = false;
+    } else {
+      long outputFileLastModified = outputFile.lastModified();
+      for (int i = 0, n = files.length; i < n; ++i) {
+        if (isNewerThan(new File(files[i]), outputFileLastModified)) {
+          skipMerge = false;
+          break;
+        }
+      }
+    }
+
+    if (skipMerge) {
+      // Nothing to do.
+      //
+      return;
+    }
+
+    try {
+      PrintWriter out = new PrintWriter(new FileWriter(outputFile), true);
+
+      out.println("<?xml version='1.0'?>");
+      out.println("<" + tag + ">");
+
+      for (int i = 0; i < files.length; i++) {
+        File file = new File(files[i]);
+        if (file.isFile()) {
+          emitFile(out, outputFile, file);
+        } else {
+          File[] children = file.listFiles();
+          if (children != null) {
+            for (int j = 0; j < children.length; ++j) {
+              if (children[j].isFile()
+                && children[j].getPath().endsWith(".xml"))
+                emitFile(out, outputFile, children[j]);
+            }
+          }
+        }
+      }
+      out.println("</" + tag + ">");
+      out.close();
+    } catch (IOException e) {
+      outputFile.deleteOnExit();
+      e.printStackTrace();
+    } catch (ParserConfigurationException e) {
+      outputFile.deleteOnExit();
+      e.printStackTrace();
+    } catch (SAXException e) {
+      outputFile.deleteOnExit();
+      e.printStackTrace();
+    } catch (TransformerException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private static boolean isNewerThan(File file, long lastModified) {
+    if (file.isFile())
+      return file.lastModified() > lastModified;
+
+    File[] children = file.listFiles();
+    if (children != null) {
+      for (int i = 0, n = children.length; i < n; ++i) {
+        File child = children[i];
+        if (isNewerThan(child, lastModified)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private static void help() {
+    System.out.println("Usage: SplitterJoiner split infile+");
+    System.out.println("Usage: SplitterJoiner join tag outfile (infile|dir)+");
+    System.out
+      .println("\tsplit         indicates that inputs file should be split into multiple output files");
+    System.out
+      .println("\tjoin          indicates xml files (or directories) should be merged into one big xml file (on stdout)");
+    System.out
+      .println("\ttag           when joining, the outermost xml tag name");
+    System.out
+      .println("\toutfile       when joining, the file to write the joined output into");
+    System.out.println("\tinfile        if splitting, an input file to split");
+    System.out
+      .println("\t              if joining, a file whose contents should be merged in");
+    System.out
+      .println("\tdir           when joining, a directory whose xml files' contents should be merged in");
+  }
+
+  public static void main(String[] args) throws IOException {
+    if (args.length < 2) {
+      help();
+      return;
+    } else if (args[0].equals("split")) {
+      String[] files = new String[args.length - 1];
+      System.arraycopy(args, 1, files, 0, args.length - 1);
+      split(files);
+    } else if (args[0].equals("join")) {
+      if (args.length < 4) {
+        help();
+        return;
+      }
+      String[] files = new String[args.length - 3];
+      System.arraycopy(args, 3, files, 0, args.length - 3);
+      merge(args[1], args[2], files);
+    } else {
+      if (!args[0].equals("-h") && !args[0].equals("-?"))
+        System.err.println("Error: don't know '" + args[0] + "'");
+      help();
+      return;
+    }
+
+  }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java
new file mode 100644
index 0000000..f2b99ba
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java
@@ -0,0 +1,73 @@
+package com.google.doctool.custom;
+
+import com.google.doctool.Booklet;
+import com.google.doctool.LinkResolver;
+import com.google.doctool.LinkResolver.ExtraClassResolver;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+
+import java.util.Map;
+
+public class ExampleTaglet implements Taglet {
+
+  public static void register(Map tagletMap) {
+    ExampleTaglet tag = new ExampleTaglet();
+    Taglet t = (Taglet) tagletMap.get(tag.getName());
+    if (t != null) {
+      tagletMap.remove(tag.getName());
+    }
+    tagletMap.put(tag.getName(), tag);
+  }
+
+  public String getName() {
+    return "example";
+  }
+
+  public boolean inConstructor() {
+    return true;
+  }
+
+  public boolean inField() {
+    return true;
+  }
+
+  public boolean inMethod() {
+    return true;
+  }
+
+  public boolean inOverview() {
+    return true;
+  }
+
+  public boolean inPackage() {
+    return true;
+  }
+
+  public boolean inType() {
+    return true;
+  }
+
+  public boolean isInlineTag() {
+    return true;
+  }
+
+  public String toString(Tag tag) {
+    SourcePosition position = LinkResolver.resolveLink(tag,
+      new ExtraClassResolver() {
+        public ClassDoc findClass(String className) {
+          return GWTJavaDoclet.root.classNamed(className);
+        }
+      });
+
+    String slurpSource = Booklet.slurpSource(position);
+    return "<blockquote><pre>" + slurpSource + "</pre></blockquote>";
+  }
+
+  public String toString(Tag[] tags) {
+    return null;
+  }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java b/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java
new file mode 100644
index 0000000..1c883f8
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java
@@ -0,0 +1,66 @@
+package com.google.doctool.custom;
+
+import com.sun.javadoc.Doclet;
+import com.sun.javadoc.RootDoc;
+import com.sun.tools.doclets.standard.Standard;
+import com.sun.tools.javadoc.Main;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GWTJavaDoclet extends Doclet {
+
+  static RootDoc root = null;
+
+  private static final String[] TAGLET_ARGS = new String[]{
+    "-taglet", ExampleTaglet.class.getName(), "-taglet",
+    TipTaglet.class.getName(), "-taglet", IncludeTaglet.class.getName()};
+
+  public static void main(String[] args) {
+    List examplePackages = new ArrayList();
+    List filteredArgs = new ArrayList();
+
+    // filter out and save packages args
+    for (int i = 0; i < args.length; ++i) {
+      if (args[i].equalsIgnoreCase("-examplepackages")) {
+        String nextArg = args[++i];
+        String[] split = nextArg.split(":|;");
+        for (int j = 0; j < split.length; ++j) {
+          examplePackages.add(split[j]);
+        }
+      } else if (args[i].equalsIgnoreCase("-packages")) {
+        String nextArg = args[++i];
+        String[] split = nextArg.split(":|;");
+        for (int j = 0; j < split.length; ++j) {
+          filteredArgs.add(split[j]);
+        }
+      } else {
+        filteredArgs.add(args[i]);
+      }
+    }
+
+    // Build a javadoc structure that includes example packages for reference
+    String name = GWTJavaDoclet.class.getName();
+    List myArgs = new ArrayList();
+    myArgs.addAll(filteredArgs);
+    myArgs.addAll(examplePackages);
+    Main.execute(name, name, (String[]) myArgs.toArray(new String[]{}));
+
+    // Now delegate to the real javadoc without the example packages
+    filteredArgs.addAll(0, Arrays.asList(TAGLET_ARGS));
+    Main.execute((String[]) filteredArgs.toArray(new String[]{}));
+  }
+
+  public static int optionLength(String option) {
+    // delegate
+    return Standard.optionLength(option);
+  }
+
+  public static boolean start(RootDoc root) {
+    // cache the root; ExampleTag will use it for reference later.
+    GWTJavaDoclet.root = root;
+    return true;
+  }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java
new file mode 100644
index 0000000..3c2e214
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java
@@ -0,0 +1,62 @@
+package com.google.doctool.custom;
+
+import com.google.doctool.ResourceIncluder;
+
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+
+import java.util.Map;
+
+public class IncludeTaglet implements Taglet {
+
+  public static void register(Map tagletMap) {
+    IncludeTaglet tag = new IncludeTaglet();
+    Taglet t = (Taglet) tagletMap.get(tag.getName());
+    if (t != null) {
+      tagletMap.remove(tag.getName());
+    }
+    tagletMap.put(tag.getName(), tag);
+  }
+
+  public String getName() {
+    return "gwt.include";
+  }
+
+  public boolean inConstructor() {
+    return true;
+  }
+
+  public boolean inField() {
+    return true;
+  }
+
+  public boolean inMethod() {
+    return true;
+  }
+
+  public boolean inOverview() {
+    return true;
+  }
+
+  public boolean inPackage() {
+    return true;
+  }
+
+  public boolean inType() {
+    return true;
+  }
+
+  public boolean isInlineTag() {
+    return true;
+  }
+
+  public String toString(Tag tag) {
+    String contents = ResourceIncluder.getResourceFromClasspathScrubbedForHTML(tag);
+    return "<blockquote><pre>" + contents + "</pre></blockquote>";
+  }
+
+  public String toString(Tag[] tags) {
+    return null;
+  }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java
new file mode 100644
index 0000000..0cc7fbe
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java
@@ -0,0 +1,23 @@
+package com.google.doctool.custom;
+
+import com.sun.tools.doclets.Taglet;
+import com.sun.tools.doclets.standard.tags.SimpleTaglet;
+
+import java.util.Map;
+
+public class TipTaglet extends SimpleTaglet {
+
+  public static void register(Map tagletMap) {
+    TipTaglet tag = new TipTaglet();
+    Taglet t = (Taglet) tagletMap.get(tag.getName());
+    if (t != null) {
+      tagletMap.remove(tag.getName());
+    }
+    tagletMap.put(tag.getName(), tag);
+  }
+
+  public TipTaglet() {
+    super("tip", "Tip:", "a");
+  }
+
+}
diff --git a/build-tools/doctool/src/doc-topics.xslt b/build-tools/doctool/src/doc-topics.xslt
new file mode 100644
index 0000000..ac0019f
--- /dev/null
+++ b/build-tools/doctool/src/doc-topics.xslt
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!-- Transforms booklet expositive documentation into Topics -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml"/>
+
+    <xsl:template match="/">
+    	<topics>
+			<xsl:apply-templates select="booklet"/>
+		</topics>
+    </xsl:template>
+
+    <xsl:template match="booklet">
+        <xsl:apply-templates select="package/*"/>
+    </xsl:template>
+
+    <xsl:template match="class | interface">
+        <topic>
+            <id><xsl:value-of select="id"/></id>
+
+            <xsl:if test="tags/order">
+                <order><xsl:value-of select="tags/order"/></order>
+            </xsl:if>
+
+            <xsl:variable name="title">
+                <xsl:value-of select="tags/title"/>
+                <xsl:if test="not(tags/title)">
+                    <xsl:comment>[Missing title]</xsl:comment>
+                </xsl:if>
+			</xsl:variable>
+
+            <title>
+            	<xsl:value-of select="$title"/>
+            </title>
+
+            <xsl:if test="location/link">
+	            <location>
+                    <xsl:for-each select="location/link">
+	                	<xsl:if test="position() > 2">
+							<span class="item"><xsl:copy-of select="."/></span> &gt;
+						</xsl:if>
+                    </xsl:for-each>
+					<span class="selected item">
+	                    <xsl:value-of select="$title"/>
+	                </span>
+	            </location>
+            </xsl:if>
+
+            <xsl:for-each select="tags/index">
+                <index><xsl:value-of select="."/></index>
+            </xsl:for-each>
+
+            <xsl:for-each select="tags/tip">
+                <xsl:copy-of select="."/>
+            </xsl:for-each>
+
+            <xsl:if test="tags/synopsis">
+                <synopsis><xsl:copy-of select="tags/synopsis/node()"/></synopsis>
+            </xsl:if>
+    
+            <body><xsl:copy-of select="description/node()"/></body>
+
+            <!-- See also -->
+            <xsl:if test="tags/link">
+				<seeAlso>
+					<xsl:for-each select="tags/link">
+						<xsl:copy-of select="."/>
+					</xsl:for-each>
+				</seeAlso>
+            </xsl:if>
+        
+            <xsl:if test="tags/childIntro">
+                <childIntro><xsl:copy-of select="tags/childIntro/node()"/></childIntro>
+            </xsl:if>
+
+            <xsl:apply-templates select="class | interface"/>
+        </topic>        
+    </xsl:template>
+
+    <xsl:template match="*|node()">
+        <!-- quiet -->
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/doc.css b/build-tools/doctool/src/doc.css
new file mode 100644
index 0000000..1418ddf
--- /dev/null
+++ b/build-tools/doctool/src/doc.css
@@ -0,0 +1,194 @@
+TABLE {
+   font-family: Arial, sans-serif;
+   font-size: small;
+}
+
+PRE {
+   font-size: medium;
+}
+
+H2.api {
+   background-color: #f5f5f5;
+}
+
+H3.api {
+   margin-bottom: 0.5em;
+}
+
+H4.api {
+   margin-bottom: 0.5em;
+}
+
+LI {
+   margin-bottom: 0.5em;
+}
+
+#nextprev {
+   float: right;
+   padding-right: 6px;
+}
+
+#nextprev nobr {
+   margin: 0 0.2em 0 0.2em;
+}
+
+DD {
+   margin-bottom: 1em;
+}
+
+DIV.tipContainer {
+   position: relative;
+   background-color: #e5ecf9;
+   padding: 0.8em 1em 1em 0em;
+   margin: 1em 0 1em 0;
+}
+
+DIV.tipCallout {
+   position: absolute;
+   padding: 0;
+   top: 0.8em;
+   left: 0.8em;
+   font-weight: bold;
+}
+
+DIV.tipBody {
+   padding-left: 3.2em;
+}
+
+DIV.topicHeader {
+   border-bottom: 2px solid blue;
+   margin: 0 1em 1em 1em;
+}
+
+DIV.topicHeader A {
+   text-decoration: none;
+}
+
+DIV.topicHeader A:hover {
+   text-decoration: underline;
+}
+
+DIV.topicHeader NOBR A {
+   text-decoration: none;
+}
+
+DIV.topicHeader NOBR A:hover {
+   text-decoration: underline;
+}
+
+DIV.topicBody {
+   margin: 1em 1em 1em 1em;
+}
+
+DIV.topicChildren {
+   margin-left: 1em;
+}
+
+DIV.topicSeeAlso {
+   margin-left: 2em;
+}
+
+DIV.topicSeeAlso H2 {
+   margin-left: -1em;
+}
+
+DIV.memberOf {
+   font-size: small;
+   margin-bottom: 1em;
+}
+
+DIV.apiSection {
+   margin-left: 2em;
+}
+
+TABLE.members {
+   font-size: 100%;
+}
+
+TABLE.members TR {
+   vertical-align: top;
+}
+
+TABLE.members TR TH {
+   font-weight: bold;
+   background-color: #CCCCCC;
+   text-align: left;
+   padding: 4px 6px 4px 6px;
+}
+
+TABLE.members TR TD {
+   background-color: white;
+   text-align: left;
+   padding: 4px 6px 4px 6px;
+   margin: 1px;
+}
+
+DIV.memberSig {
+   font-family: "bogus font here" , monospace;
+   font-size: 100%;
+   margin-bottom: 1em;
+   overflow: auto;
+}
+
+DIV.classSig {
+   color: #007000;
+   font-family: "bogus font here" , monospace;
+   font-size: 100%;
+   background-color: #f5f5f5;
+   padding: 0.5em;
+   margin-bottom: 1em;
+   overflow: auto;
+}
+
+UL.css LI {
+   font-style: normal;
+   font-family: Courier New;
+   list-style-type: none;
+}
+
+DL.fixed DT {
+   font-style: normal;
+   font-family: Courier New;
+}
+
+DL.memberDetail {
+   margin-top: 0em;
+}
+
+DL.memberDetail DT {
+   font-family: "bogus font here" , monospace;
+}
+
+DL.memberDetail DD {
+   margin-bottom: 1em;
+}
+
+TD {
+   padding: 0.2em;
+   vertical-align: top;
+}
+
+TH {
+   text-align: left;
+}
+
+TR.gallery-link TD {
+   text-align: center;
+   vertical-align: bottom;
+   padding-top: 1em;
+}
+
+TR.gallery TD {
+   text-align: center;
+   vertical-align: top;
+   padding-top: 0.25em;
+   padding-bottom: 1em;
+   padding-left: 1em;
+   padding-right: 1em;
+}
+
+IMG.gallery {
+   border: 1px solid #95BBFC;
+   padding: 4px;
+}
+
diff --git a/build-tools/doctool/src/java-topics.xslt b/build-tools/doctool/src/java-topics.xslt
new file mode 100644
index 0000000..10fb8e6
--- /dev/null
+++ b/build-tools/doctool/src/java-topics.xslt
@@ -0,0 +1,459 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml" />
+    <xsl:strip-space elements="*"/>
+    
+    <xsl:param name="id"/>
+    <xsl:param name="name"/>
+    <xsl:param name="synopsis"/>
+    <xsl:param name="childIntro"/>
+
+    <xsl:key name="extendsInterface" match="//interface/superinterface" use="@ref"/>
+    <xsl:key name="implementsInterface" match="//class/superinterface" use="@ref"/>
+    <xsl:key name="extendsClass" match="//class/superclass" use="@ref"/>
+
+    <xsl:template match="/">
+        <topics>
+           <xsl:apply-templates select="booklet"/>
+        </topics>
+    </xsl:template>
+
+    <xsl:template match="booklet">
+      <topic>
+            <xsl:copy-of select="id"/>
+            <title><xsl:value-of select="name"/></title>
+           <location>
+               <span class="selected item">
+                  <xsl:value-of select="name"/>
+               </span>
+         </location>
+            <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+            <header><h1><xsl:apply-templates select="name/node()"/></h1></header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:apply-templates select="description/node()"/>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="package"/>
+                    <xsl:with-param name="title">Packages</xsl:with-param>
+                </xsl:call-template>
+            </body>
+            <xsl:apply-templates select="package">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+      </topic>
+    </xsl:template>
+
+    <xsl:template name="emit-location">
+       <xsl:if test="location">
+           <location>
+            <xsl:for-each select="location/link">
+               <span class="item"><xsl:copy-of select="."/></span> &gt;
+            </xsl:for-each>
+            <span class="selected item">
+                  <xsl:value-of select="name"/>
+               </span>
+           </location>
+      </xsl:if>
+    </xsl:template>
+
+    <xsl:template match="package">
+        <topic>
+            <id><xsl:value-of select="id"/></id>
+            <title><xsl:value-of select="name"/></title>
+            <xsl:call-template name="emit-location"/>
+            <index><xsl:value-of select="name"/> package</index>
+            <xsl:if test="lead/node()">
+                <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+            </xsl:if>
+            <header>
+                <xsl:call-template name="memberHeader">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+            </header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:apply-templates select="description/node()"/>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="class"/>
+                    <xsl:with-param name="title">Classes</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="interface"/>
+                    <xsl:with-param name="title">Interfaces</xsl:with-param>
+                </xsl:call-template>
+            </body>
+            <xsl:apply-templates select="class">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+            <xsl:apply-templates select="interface">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+        </topic>        
+    </xsl:template>
+
+    <xsl:template match="class|interface">
+        <topic>
+            <xsl:copy-of select="id"/>
+            <title><xsl:value-of select="name"/></title>
+            <xsl:call-template name="emit-location"/>
+            <index>
+                <xsl:value-of select="name"/>
+                <xsl:if test="name()='class'"> class</xsl:if>
+                <xsl:if test="name()='interface'"> interface</xsl:if>
+            </index>
+            <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+            <header>
+                <xsl:call-template name="memberHeader">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+            </header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:apply-templates select="description/node()"/>
+            
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="class"/>
+                    <xsl:with-param name="title">Nested Classes</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="interface"/>
+                    <xsl:with-param name="title">Nested Interfaces</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="field"/>
+                    <xsl:with-param name="title">Fields</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="constructor"/>
+                    <xsl:with-param name="title">Constructors</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="method"/>
+                    <xsl:with-param name="title">Methods</xsl:with-param>
+                </xsl:call-template>
+
+                <!-- See also -->
+                <xsl:if test="tags/link">
+                    <h2 class="api">See Also</h2>
+                     <xsl:for-each select="tags/link">
+                           <xsl:copy-of select="."/>
+                           <xsl:if test="position()!=last()">, </xsl:if> 
+                     </xsl:for-each>
+                </xsl:if>
+
+               <xsl:for-each select="field[not(jre)]">
+                   <xsl:sort select="name"/>
+                   <xsl:if test="position() = 1">
+                     <h2 class="api">Field Detail</h2>
+                   </xsl:if>
+                   <xsl:apply-templates select="."/>
+                   <xsl:if test="position() != last()">
+                     <hr/>
+                   </xsl:if>
+               </xsl:for-each>
+   
+               <xsl:for-each select="constructor[not(jre)]">
+                   <xsl:sort select="name"/>
+                   <xsl:if test="position() = 1">
+                     <h2 class="api">Constructor Detail</h2>
+                   </xsl:if>
+                   <xsl:apply-templates select="."/>
+                   <xsl:if test="position() != last()">
+                     <hr/>
+                   </xsl:if>
+               </xsl:for-each>
+   
+               <xsl:for-each select="method[not(jre)]">
+                   <xsl:sort select="name"/>
+                   <xsl:if test="position() = 1">
+                     <h2 class="api">Method Detail</h2>
+                   </xsl:if>
+                   <xsl:apply-templates select="."/>
+                   <xsl:if test="position() != last()">
+                     <hr/>
+                   </xsl:if>
+               </xsl:for-each>
+                
+            </body>
+
+            <xsl:apply-templates select="interface">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+
+            <xsl:apply-templates select="class">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+        </topic>
+    </xsl:template>
+
+    <xsl:template match="method|constructor">
+      <!-- Anchor -->
+      <xsl:if test="string-length(substring-after(id, &quot;#&quot;)) != 0">
+         <a name="{substring-after(id, &quot;#&quot;)}"/>
+      </xsl:if>
+      
+      <!-- The simple name -->
+      <h3 class="api"><xsl:value-of select="name"/></h3>    
+    
+      <!-- The signature -->
+      <div class="memberSig">
+         <xsl:call-template name="memberSig">
+            <xsl:with-param name="member" select="."/>
+         </xsl:call-template>
+      </div>
+   
+      <!-- Lengthier description -->
+      <xsl:apply-templates select="description/node()"/>
+
+      <!-- Parameter description -->
+      <xsl:if test="params/param">
+         <h4 class="api">Parameters</h4>
+         <dl class="memberDetail">
+         <xsl:for-each select="params/param">
+            <dt><xsl:value-of select="name"/></dt>
+            <dd>
+               <xsl:variable name="paramDesc" select="../../tags/param[name=current()/name]/description/node()"/>
+               <xsl:apply-templates select="$paramDesc"/>
+               <xsl:if test="not($paramDesc)"><xsl:comment>[Missing documentation]</xsl:comment></xsl:if>
+            </dd>
+         </xsl:for-each>
+         </dl>
+      </xsl:if>
+
+      <!-- Return value description -->
+      <xsl:if test="tags/return">
+         <h4 class="api">Return Value</h4>
+         <xsl:apply-templates select="tags/return/node()"/>
+      </xsl:if>
+
+      <!-- See also -->
+      <xsl:if test="tags/link">
+         <h4 class="api">See Also</h4>
+         <xsl:for-each select="tags/link">
+            <xsl:copy-of select="."/>
+            <xsl:if test="position()!=last()">, </xsl:if> 
+         </xsl:for-each>
+      </xsl:if>
+    </xsl:template>
+
+    <xsl:template match="field">
+      <!-- Anchor -->
+      <xsl:if test="string-length(substring-after(id, &quot;#&quot;)) != 0">
+         <a name="#{substring-after(id, &quot;#&quot;)}"/>
+      </xsl:if>
+
+      <!-- The simple name -->
+      <h3 class="api"><xsl:value-of select="name"/></h3>    
+
+      <!-- The signature -->
+      <div class="memberSig">
+         <xsl:call-template name="memberSig">
+            <xsl:with-param name="member" select="."/>
+         </xsl:call-template>
+      </div>
+
+      <!-- Lengthier description -->
+      <xsl:apply-templates select="description/node()"/>
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+      <xsl:copy>
+         <xsl:apply-templates select="@*|node()"/>
+      </xsl:copy>
+    </xsl:template>
+
+    <xsl:template name="memberTable">
+        <xsl:param name="members"/>
+        <xsl:param name="title"/>
+        
+        <xsl:if test="$members">
+            <h2 class="api"><xsl:value-of select="$title"/></h2>
+            <table cellspacing="1" cellpadding="1" class="members">
+                    <xsl:apply-templates select="$members" mode="memberTableImpl">
+                  <xsl:sort case-order="lower-first" data-type="text" order="ascending" select="name"/>
+                    </xsl:apply-templates>
+            </table>
+        </xsl:if>
+    </xsl:template>
+
+   <xsl:template match="*" mode="memberTableImpl">
+      <tr>
+         <td>
+            <xsl:choose>
+               <xsl:when test="jre and (name()='method' or name()='constructor' or name()='field')">
+                  <a href="{jre}"><xsl:value-of select="name"/><xsl:value-of select="flatSignature"/></a>
+               </xsl:when>
+               <xsl:otherwise>
+                  <link ref="{id}"><xsl:value-of select="name"/><xsl:value-of select="flatSignature"/></link>
+               </xsl:otherwise>
+            </xsl:choose>
+         </td>
+         <td>
+            <xsl:apply-templates select="lead/node()"/><xsl:text> </xsl:text>
+         </td>
+      </tr>
+   </xsl:template>
+
+    <xsl:template name="memberHeader">
+        <xsl:param name="member"/>
+        <xsl:variable name="parent" select="$member/.."/>
+
+        <!-- The header announces the simple name of the member -->
+        <h1>
+            <xsl:if test="name()='package'">Package </xsl:if>
+            <xsl:if test="name()='class'">Class </xsl:if>
+            <xsl:if test="name()='interface'">Interface </xsl:if>
+            <xsl:if test="name()='method'">Method </xsl:if>
+            <xsl:if test="name()='constructor'">Constructor </xsl:if>
+            <xsl:if test="name()='field'">Field </xsl:if>
+            <xsl:value-of select="name"/>
+        </h1>
+
+        <xsl:if test="name()!='package'">
+            <!-- A signature for the member -->
+            <div class="classSig">
+                <xsl:call-template name="memberSig">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+                
+               <!-- Show the implementing classes and extending interfaces -->
+               <xsl:if test="name()='interface'">
+                  <xsl:variable name="id" select="id"/>
+                  <xsl:for-each select="key('extendsInterface', $id)/..">
+                     <xsl:if test="position() = 1">
+                        <br/><br/>
+                        <xsl:text>// Extended by </xsl:text>
+                     </xsl:if>
+                     <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+                     <xsl:if test="position() != last()">, </xsl:if>
+                  </xsl:for-each>
+
+                  <xsl:for-each select="key('implementsInterface', $id)/..">
+                     <xsl:if test="position() = 1">
+                        <xsl:if test="not(key('extendsInterface', $id)/..)">
+                           <!-- Extra break needed -->
+                           <br/>
+                        </xsl:if>
+                        <br/>
+                        <xsl:text>// Implemented by </xsl:text>
+                     </xsl:if>
+                     <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+                     <xsl:if test="position() != last()">, </xsl:if>
+                  </xsl:for-each>
+               </xsl:if>
+
+               <!-- Show the derived classes -->
+               <xsl:if test="name()='class'">
+                  <xsl:variable name="id" select="id"/>
+                  <xsl:if test="name!='Object' and key('extendsClass', $id)">
+                     <br/><br/>
+                     <xsl:text>// Superclass of </xsl:text>
+                     <xsl:for-each select="key('extendsClass', $id)/..">
+                        <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+                        <xsl:if test="position() != last()">, </xsl:if>
+                     </xsl:for-each>
+                  </xsl:if>
+               </xsl:if>
+            </div>
+        </xsl:if>
+
+    </xsl:template>
+    
+    <xsl:template name="memberSig">
+        <xsl:param name="member"/>
+
+        <xsl:if test="isPublic">public </xsl:if>
+        <xsl:if test="isProtected">protected </xsl:if>
+        <xsl:if test="isPrivate">private </xsl:if>
+        <xsl:if test="isPackagePrivate">/*package*/ </xsl:if>
+        <xsl:if test="isStatic">static </xsl:if>
+        <xsl:if test="isFinal">final </xsl:if>
+        <xsl:if test="isAbstract">abstract </xsl:if>
+        <xsl:if test="isSynchronized">synchronized </xsl:if>
+
+        <xsl:if test="name()='class'">
+            class 
+            <xsl:value-of select="name"/>
+            <xsl:if test="superclass">
+                <br/>extends
+                <link ref="{superclass/@ref}"><xsl:value-of select="superclass"/></link>
+            </xsl:if>
+            <xsl:if test="superinterface">
+                <br/>implements 
+                <xsl:for-each select="superinterface">
+                    <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    <xsl:if test="position() != last()">, </xsl:if>
+                </xsl:for-each>
+            </xsl:if>
+        </xsl:if>
+
+        <xsl:if test="name()='interface'">
+            interface 
+            <b><xsl:value-of select="name"/></b>
+            <xsl:if test="superinterface">
+                <br/>extends 
+                <xsl:for-each select="superinterface">
+                    <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    <xsl:if test="position() != last()">, </xsl:if>
+                </xsl:for-each>
+            </xsl:if>
+        </xsl:if>
+
+        <xsl:if test="name()='field'">
+            <xsl:if test="@ref">
+                <link ref="{@ref}"><xsl:value-of select="type"/></link>
+            </xsl:if>
+            <xsl:if test="not(@ref)">
+                <xsl:value-of select="type"/>
+            </xsl:if>
+            <xsl:text> </xsl:text>
+            <b><xsl:value-of select="name"/></b>
+        </xsl:if>
+
+        <xsl:if test="name()='method' or name()='constructor'">
+            <xsl:if test="type/@ref">
+                <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+            </xsl:if>
+            <xsl:if test="not(type/@ref)">
+                <xsl:value-of select="type"/>
+            </xsl:if>
+            <b><xsl:text> </xsl:text><xsl:value-of select="name"/></b><xsl:text>(</xsl:text>
+            <xsl:if test="params/param">
+               <xsl:for-each select="params/param">
+                  <nobr>
+                  <xsl:if test="type/@ref">
+                     <link ref="{type/@ref}"><xsl:value-of select="type"/></link> 
+                  </xsl:if>
+                  <xsl:if test="not(type/@ref)">
+                     <xsl:value-of select="type"/> 
+                  </xsl:if>
+                  <xsl:text> </xsl:text>
+                  <xsl:value-of select="name"/> 
+                  <xsl:if test="position() != last()">, </xsl:if>
+                  </nobr>
+               </xsl:for-each>
+            </xsl:if><xsl:text>)</xsl:text>
+            <xsl:if test="throws/throw">
+               <br/>&#160;&#160;&#160;&#160;
+                throws
+                <xsl:for-each select="throws/throw">
+                    <xsl:text> </xsl:text>
+                    <xsl:if test="@ref">
+                        <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    </xsl:if>
+                    <xsl:if test="not(@ref)">
+                        <xsl:value-of select="."/>
+                    </xsl:if>
+                    <xsl:if test="position() != last()">,</xsl:if>
+                </xsl:for-each>
+            </xsl:if>            
+        </xsl:if>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/javascript-topics.xslt b/build-tools/doctool/src/javascript-topics.xslt
new file mode 100644
index 0000000..383ae50
--- /dev/null
+++ b/build-tools/doctool/src/javascript-topics.xslt
@@ -0,0 +1,420 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="xml" />
+    <xsl:strip-space elements="*"/>
+    
+    <xsl:param name="id"/>
+    <xsl:param name="name"/>
+    <xsl:param name="synopsis"/>
+    <xsl:param name="childIntro"/>
+
+    <xsl:template match="*|node()">
+		<!-- no echo -->
+    </xsl:template>
+
+    <xsl:template match="/">
+        <xsl:apply-templates select="booklet"/>
+    </xsl:template>
+
+    <xsl:template match="booklet">
+        <topic>
+            <xsl:copy-of select="id"/>
+            <title><xsl:value-of select="name"/></title>
+            <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+            <header><h1><xsl:copy-of select="name/node()"/></h1></header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:copy-of select="description/node()"/>
+            <xsl:apply-templates select="package">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+            </body>
+        </topic>
+    </xsl:template>
+
+    <xsl:template match="package">
+		<xsl:call-template name="memberTable">
+			<xsl:with-param name="members" select="class"/>
+			<xsl:with-param name="title">Classes</xsl:with-param>
+		</xsl:call-template>
+<!--
+		<xsl:call-template name="memberTable">
+			<xsl:with-param name="members" select="interface"/>
+			<xsl:with-param name="title">Interfaces</xsl:with-param>
+		</xsl:call-template>
+-->
+		<xsl:apply-templates select="class">
+			<xsl:sort select="name"/>
+		</xsl:apply-templates>
+    </xsl:template>
+
+    <xsl:template match="class|interface">
+        <topic>
+            <xsl:copy-of select="id"/>
+            <title><xsl:value-of select="name"/></title>
+            <index>
+                <xsl:value-of select="name"/>
+                <xsl:if test="name()='class'"> class</xsl:if>
+                <xsl:if test="name()='interface'"> interface</xsl:if>
+            </index>
+            <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+            <header>
+                <xsl:call-template name="memberHeader">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+            </header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:copy-of select="description/node()"/>
+            
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="class"/>
+                    <xsl:with-param name="title">Nested classes</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="interface"/>
+                    <xsl:with-param name="title">Nested interfaces</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="field"/>
+                    <xsl:with-param name="title">Fields</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="constructor"/>
+                    <xsl:with-param name="title">Constructors</xsl:with-param>
+                </xsl:call-template>
+
+                <xsl:call-template name="memberTable">
+                    <xsl:with-param name="members" select="method"/>
+                    <xsl:with-param name="title">Methods</xsl:with-param>
+                </xsl:call-template>
+
+                <!-- See also -->
+                <xsl:if test="tags/link">
+                    <h2>See Also</h2>
+                    <div class="apiSection">
+                        <xsl:for-each select="tags/link">
+                            <xsl:copy-of select="."/>
+                            <xsl:if test="position()!=last()">, </xsl:if> 
+                        </xsl:for-each>
+                    </div>
+                </xsl:if>
+            </body>
+
+            <xsl:apply-templates select="interface">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+
+            <xsl:apply-templates select="class">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+
+            <xsl:apply-templates select="field">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+
+            <xsl:apply-templates select="constructor">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+
+            <xsl:apply-templates select="method">
+                <xsl:sort select="name"/>
+            </xsl:apply-templates>
+        </topic>
+    </xsl:template>
+
+    <xsl:template match="method|constructor">
+        <topic>
+            <id><xsl:value-of select="id"/></id>
+            <title>
+				<xsl:call-template name="emitFlatSig">
+					<xsl:with-param name="methodOrCtor" select="current()"/>
+				</xsl:call-template>
+			</title>
+            <index>
+                <xsl:value-of select="name"/>
+                <xsl:if test="name()='method'"> method</xsl:if>
+                <xsl:if test="name()='constructor'"> constructor</xsl:if>
+            </index>
+            <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+            <header>
+                <xsl:call-template name="memberHeader">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+            </header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:copy-of select="description/node()"/>
+
+                <!-- Parameter description -->
+                <xsl:if test="params/param">
+                    <h2>Parameters</h2>
+                    <div class="apiSection">
+                        <dl>
+                        <xsl:for-each select="params/param">
+                            <dt>							
+								<xsl:if test="starts-with(name, '_')">
+									<xsl:value-of select="substring(name,2)"/><xsl:text>?</xsl:text>
+								</xsl:if>
+								<xsl:if test="not(starts-with(name, '_'))">
+									<xsl:value-of select="name"/> 
+								</xsl:if>
+                            </dt>
+                            <dd>
+								<xsl:if test="starts-with(name, '_')">
+									<em>[Optional] </em>
+								</xsl:if>
+                                <xsl:variable name="paramDesc" select="../../tags/param[name=current()/name]/description/node()"/>
+                                <xsl:copy-of select="$paramDesc"/>
+                                <xsl:if test="not($paramDesc)"><xsl:comment>[Missing documentation]</xsl:comment></xsl:if>
+                            </dd>
+                        </xsl:for-each>
+                        </dl>
+                    </div>
+                </xsl:if>
+
+                <!-- Return value description -->
+                <xsl:if test="tags/return">
+                    <h2>Return Value</h2>
+                    <div class="apiSection">
+                        <xsl:copy-of select="tags/return/node()"/>
+                    </div>
+                </xsl:if>
+
+                <!-- See also -->
+                <xsl:if test="tags/link">
+                    <h2>See Also</h2>
+                    <div class="apiSection">
+                        <xsl:for-each select="tags/link">
+                            <xsl:copy-of select="."/>
+                            <xsl:if test="position()!=last()">, </xsl:if> 
+                        </xsl:for-each>
+                    </div>
+                </xsl:if>
+            </body>
+        </topic>
+    </xsl:template>
+
+    <xsl:template match="field">
+        <topic>
+            <id><xsl:value-of select="id"/></id>
+            <title><xsl:value-of select="name"/></title>
+            <index><xsl:value-of select="name"/> field</index>
+            <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+            <header>
+                <xsl:call-template name="memberHeader">
+                    <xsl:with-param name="member" select="."/>
+                </xsl:call-template>
+            </header>
+            <body>
+                <!-- Lengthier description -->
+                <xsl:copy-of select="description/node()"/>
+            </body>
+        </topic>
+    </xsl:template>
+
+    <xsl:template match="*|node()">
+        <!-- quiet -->
+    </xsl:template>
+    
+    <xsl:template name="memberTable">
+        <xsl:param name="members"/>
+        <xsl:param name="title"/>
+        
+        <xsl:if test="$members">
+            <h2><xsl:value-of select="$title"/></h2>
+            <table class="members" cellspacing="1" cellpadding="0">
+                    <xsl:apply-templates select="$members" mode="memberTableImpl">
+						<xsl:sort case-order="lower-first" data-type="text" order="ascending" select="."/>
+                    </xsl:apply-templates>
+            </table>
+        </xsl:if>
+    </xsl:template>
+
+	<xsl:template match="*" mode="memberTableImpl">
+		<tr>
+			<td width="1%">
+				<nobr>
+					<link ref="{id}">
+						<xsl:call-template name="emitFlatSig">
+							<xsl:with-param name="methodOrCtor" select="current()"/>
+						</xsl:call-template>
+					</link>
+				</nobr>
+			</td>
+			<td><xsl:copy-of select="lead/node()"/><xsl:text> </xsl:text></td>
+		</tr>
+	</xsl:template>
+
+    <xsl:template name="memberHeader">
+        <xsl:param name="member"/>
+        <xsl:variable name="parent" select="$member/.."/>
+
+        <div class="memberHeader">
+
+            <!-- The header announces the simple name of the member -->
+            <h1>
+                <xsl:if test="name()='package'">Package </xsl:if>
+                <xsl:if test="name()='class'">Class </xsl:if>
+                <xsl:if test="name()='interface'">Interface </xsl:if>
+                <xsl:if test="name()='method'">Method </xsl:if>
+                <xsl:if test="name()='constructor'">Constructor </xsl:if>
+                <xsl:if test="name()='field'">Field </xsl:if>
+                <xsl:value-of select="name"/>
+            </h1>
+
+            <xsl:if test="name()!='package'">
+                <!-- Tell where this member lives -->
+<!--
+                <div class="memberOf">
+                    Member of <link ref="{$parent/id}"><xsl:value-of select="$parent/name"/></link>
+                </div>
+-->            
+                <!-- A signature for the member -->
+                <code class="signature">
+                    <xsl:call-template name="memberSig">
+                        <xsl:with-param name="member" select="."/>
+                    </xsl:call-template>
+                </code>
+            </xsl:if>
+        </div>
+        
+    </xsl:template>
+    
+    <xsl:template name="memberSig">
+        <xsl:param name="member"/>
+
+<!--
+        <xsl:if test="isPublic">public </xsl:if>
+        <xsl:if test="isProtected">protected </xsl:if>
+        <xsl:if test="isPrivate">private </xsl:if>
+        <xsl:if test="isPackagePrivate">/*package*/ </xsl:if>
+        <xsl:if test="isStatic">static </xsl:if>
+        <xsl:if test="isFinal">final </xsl:if>
+        <xsl:if test="isAbstract">abstract </xsl:if>
+        <xsl:if test="isSynchronized">synchronized </xsl:if>
+-->
+        <xsl:if test="name()='class'">
+            class 
+            <b><xsl:value-of select="name"/></b>
+<!--
+            <xsl:if test="superclass[@ref != 'java.lang.Object']">
+                <br/>derives from 
+                <link ref="{superclass/@ref}"><xsl:value-of select="superclass"/></link>
+            </xsl:if>
+-->
+<!--            
+            <xsl:if test="superinterface">
+                <br/>can be used as 
+                <xsl:for-each select="superinterface">
+                    <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    <xsl:if test="position() != last()">, </xsl:if>
+                </xsl:for-each>
+            </xsl:if>
+-->
+        </xsl:if>
+<!--        
+        <xsl:if test="name()='interface'">
+            interface 
+            <b><xsl:value-of select="name"/></b>
+            <xsl:if test="superinterface">
+                <br/>extends 
+                <xsl:for-each select="superinterface">
+                    <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    <xsl:if test="position() != last()">, </xsl:if>
+                </xsl:for-each>
+            </xsl:if>
+        </xsl:if>
+-->
+        <xsl:if test="name()='field'">
+            <xsl:if test="@ref">
+                <link ref="{@ref}"><xsl:value-of select="type"/></link>
+            </xsl:if>
+            <xsl:if test="not(@ref)">
+                <xsl:value-of select="type"/>
+            </xsl:if>
+            <xsl:text> </xsl:text>
+            <b><xsl:value-of select="name"/></b>
+        </xsl:if>
+
+        <xsl:if test="name()='method' or name()='constructor'">
+<!--        
+            <xsl:if test="type/@ref">
+                <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+            </xsl:if>
+            <xsl:if test="not(type/@ref)">
+                <xsl:value-of select="type"/>
+            </xsl:if>
+-->
+            <b><xsl:value-of select="name"/></b><xsl:text>(</xsl:text>
+            <xsl:if test="params/param">
+					<xsl:for-each select="params/param">
+<!--						   
+						<div style="margin-left: 2em">
+						   <nobr>
+							<xsl:if test="type/@ref">
+								<link ref="{type/@ref}"><xsl:value-of select="type"/></link> 
+							</xsl:if>
+							<xsl:if test="not(type/@ref)">
+								<xsl:value-of select="type"/> 
+							</xsl:if>
+							<xsl:text> </xsl:text>
+-->
+							<xsl:if test="starts-with(name, '_')">
+								<xsl:value-of select="substring(name,2)"/><xsl:text>?</xsl:text>
+							</xsl:if>
+							<xsl:if test="not(starts-with(name, '_'))">
+								<xsl:value-of select="name"/> 
+							</xsl:if>
+							<xsl:if test="position() != last()">, </xsl:if>
+<!--							
+							</nobr>
+						</div>
+-->
+					</xsl:for-each>
+            </xsl:if>
+            <xsl:text>)</xsl:text>
+            <xsl:if test="throws/throw">
+                <br/>
+                throws
+                <xsl:for-each select="throws/throw">
+                    <xsl:text> </xsl:text>
+                    <xsl:if test="@ref">
+                        <link ref="{@ref}"><xsl:value-of select="."/></link>
+                    </xsl:if>
+                    <xsl:if test="not(@ref)">
+                        <xsl:value-of select="."/>
+                    </xsl:if>
+                    <xsl:if test="position() != last()">,</xsl:if>
+                </xsl:for-each>
+            </xsl:if>            
+        </xsl:if>
+    </xsl:template>
+
+	<xsl:template name="emitFlatSig">
+		<xsl:param name="methodOrCtor"/>
+		<xsl:value-of select="$methodOrCtor/name"/>
+		<xsl:text>(</xsl:text>
+			<xsl:for-each select="$methodOrCtor/params/param">
+				<xsl:call-template name="emitParamName">
+					<xsl:with-param name="param" select="."/>
+				</xsl:call-template>
+                    <xsl:if test="position() != last()">, </xsl:if>
+			</xsl:for-each>
+		<xsl:text>)</xsl:text>
+	</xsl:template>
+
+	<xsl:template name="emitParamName">
+		<xsl:param name="param"/>
+		<xsl:if test="starts-with($param/name, '_')">
+			<xsl:value-of select="substring($param/name,2)"/><xsl:text>?</xsl:text>
+		</xsl:if>
+		<xsl:if test="not(starts-with($param/name, '_'))">
+			<xsl:value-of select="$param/name"/> 
+		</xsl:if>
+	</xsl:template>
+    
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhc.xslt b/build-tools/doctool/src/topics-hhc.xslt
new file mode 100644
index 0000000..8ce3bd3
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhc.xslt
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html"/>
+    <xsl:template match="/">
+        <html>
+            <body>
+                <ul>
+                    <xsl:apply-templates select="topic">
+                        <xsl:sort select="order" data-type="number" order="descending"/>
+                    </xsl:apply-templates>
+                </ul>
+            </body>
+        </html>
+    </xsl:template>
+    
+    <xsl:template match="topic">
+        <li>
+            <object type="text/sitemap">
+                <xsl:element name="param">
+                    <xsl:attribute name="name">Name</xsl:attribute>
+                    <xsl:attribute name="value"><xsl:value-of select="title" /></xsl:attribute>
+                </xsl:element>
+                <xsl:element name="param">
+                    <xsl:attribute name="name">Local</xsl:attribute>
+                    <xsl:attribute name="value"><xsl:value-of select="id" />.html</xsl:attribute>
+                </xsl:element>
+            </object>
+        </li>
+        <xsl:if test="topic">
+            <ul>
+                <xsl:apply-templates select="topic" />
+            </ul>
+        </xsl:if>
+    </xsl:template>
+
+    <!-- If a topic doesn't have a title, we pretend it isn't there and hoist its children up in its place -->
+    <xsl:template match="topic[not(title)]">
+        <xsl:apply-templates select="topic">
+            <xsl:sort select="order" data-type="number" order="descending"/>        
+        </xsl:apply-templates>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhk.xslt b/build-tools/doctool/src/topics-hhk.xslt
new file mode 100644
index 0000000..ada0d3b
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhk.xslt
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html"/>
+    <xsl:template match="/">
+        <html>
+            <body>
+                <ul>
+                    <xsl:for-each select="//topic/index">
+                        <xsl:sort select="."/>
+                        <li>
+                            <object type="text/sitemap">
+                                <xsl:element name="param">
+                                    <xsl:attribute name="name">Name</xsl:attribute>
+                                    <xsl:attribute name="value">
+                                        <xsl:value-of select="." />
+                                    </xsl:attribute>
+                                </xsl:element>
+                                <xsl:element name="param">
+                                    <xsl:attribute name="name">Local</xsl:attribute>
+                                    <xsl:attribute name="value"><xsl:value-of select="../id" />.html</xsl:attribute>
+                                </xsl:element>
+                            </object>
+                        </li>
+                    </xsl:for-each>
+                </ul>
+            </body>
+        </html>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhp.xslt b/build-tools/doctool/src/topics-hhp.xslt
new file mode 100644
index 0000000..980a1eb
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhp.xslt
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="text"/>
+
+    <xsl:param name="title"/>
+    <xsl:param name="filebase"/>
+    <xsl:param name="chm"><xsl:value-of select="$filebase"/>.chm</xsl:param>
+    <xsl:param name="hhc"><xsl:value-of select="$filebase"/>.hhc</xsl:param>
+    <xsl:param name="hhk"><xsl:value-of select="$filebase"/>.hhk</xsl:param>
+    <xsl:param name="css"><xsl:value-of select="$filebase"/>.css</xsl:param>
+    <xsl:template match="/">
+[Options]
+Compatibility = 1.1 Or later
+Compiled file = <xsl:value-of select="$chm"/>
+Contents file = <xsl:value-of select="$hhc"/>
+Default Font = Arial, 9, 0
+Default Window=Main
+Display compile notes=No
+Display compile progress=No
+Error log file=error.log
+Full-text search=Yes
+Index file = <xsl:value-of select="$hhk"/>
+Language=0x409 English (United States)
+Title=<xsl:value-of select="$title"/>
+
+[Windows]
+Main=&quot;<xsl:value-of select="$title"/>&quot;,&quot;<xsl:value-of select="$hhc"/>&quot;,&quot;<xsl:value-of select="$hhk"/>&quot;,,,,,,,0x62520,,0x304c,[10,10,610,510],,,,,,,0
+
+[Files]
+<xsl:for-each select="//topic[title]">
+<xsl:sort select="order" data-type="number" order="descending"/>        
+<xsl:value-of select="id"/>.html
+</xsl:for-each>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-htmls.xslt b/build-tools/doctool/src/topics-htmls.xslt
new file mode 100644
index 0000000..70b8584
--- /dev/null
+++ b/build-tools/doctool/src/topics-htmls.xslt
@@ -0,0 +1,331 @@
+<?xml version="1.0"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:output method="html"/>
+  <xsl:strip-space elements="*"/>
+  <xsl:key name="topicSearch" match="//topic" use="id"/>
+  <xsl:param name="css"/>
+  <xsl:param name="title"/>
+  <xsl:template match="/">
+@FILE
+        <xsl:apply-templates select="//topic"/>
+  </xsl:template>
+  <xsl:template name="gen-side">
+    <div id="side">
+      <div id="menu">
+        <div class="header item">
+          <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>
+        </div>
+        <div class="group">
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/overview.html">Product Overview</a>
+          </div>
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/gettingstarted.html">Getting Started Guide</a>
+          </div>
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/download.html">Download SDK</a>
+          </div>
+        </div>
+        <div class="group">
+          <xsl:element name="div">
+            <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="contains(id, 'com.google.gwt.doc')"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+            <a href="./com.google.gwt.doc.DeveloperGuide.html">Developer Guide</a>
+          </xsl:element>
+          <div class="group">
+            <div class="item">
+              <a href="http://code.google.com/webtoolkit/documentation/examples/">Example Projects</a>
+            </div>
+            <xsl:element name="div">
+              <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="(contains(id, 'com.google.gwt.') and not(contains(id, 'com.google.gwt.doc.'))) or id = 'gwt'"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+              <a href="./gwt.html">GWT Class Reference</a>
+            </xsl:element>
+            <xsl:element name="div">
+              <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="contains(id, 'java.') or id='jre'"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+              <a href="./jre.html">JRE Emulation Library</a>
+            </xsl:element>
+          </div>
+        </div>
+        <div class="group">
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/faq.html">Web Toolkit FAQ</a>
+          </div>
+          <div class="item">
+            <a href="http://googlewebtoolkit.blogspot.com/">Web Toolkit Blog</a>
+          </div>
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/thirdparty.html">Third Party Tools</a>
+          </div>
+          <div class="item">
+            <a href="http://code.google.com/webtoolkit/issues/">Issue Tracking</a>
+          </div>
+          <div class="item">
+            <a href="http://groups.google.com/group/Google-Web-Toolkit">Developer Forum</a>
+          </div>
+        </div>
+      </div>
+      <div id="search">
+        <form action="http://www.google.com/search" method="get">
+          <div>
+            <input name="domains" value="code.google.com" type="hidden"/>
+            <input name="sitesearch" value="code.google.com" type="hidden"/>
+            <div class="header">Search this site:</div>
+            <div class="input">
+              <input name="q" size="10"/>
+            </div>
+            <div class="button">
+              <input value="Search" type="submit"/>
+            </div>
+          </div>
+        </form>
+      </div>
+    </div>
+  </xsl:template>
+  <xsl:template name="gen-header">
+    <div id="gaia">&#160;</div>
+    <div id="header">
+      <div id="logo">
+        <a href="http://code.google.com/">
+          <img src="http://code.google.com/images/code_sm.png" alt="Google"/>
+        </a>
+      </div>
+      <!-- The title for the entire docset -->
+      <div id="title">
+        <xsl:value-of select="$title"/>
+      </div>
+      <div id="breadcrumbs">
+        <div id="nextprev">
+          <xsl:call-template name="emitPrevTopic">
+            <xsl:with-param name="start" select="."/>
+          </xsl:call-template>
+          <xsl:text> </xsl:text>
+          <xsl:call-template name="emitNextTopic">
+            <xsl:with-param name="start" select="."/>
+          </xsl:call-template>
+        </div>
+        <span class="item">
+          <a href="http://code.google.com/">Google Code Home</a>
+        </span> &gt;
+				<span class="item">
+          <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>
+        </span> &gt;
+				<!-- The topic location -->
+        <xsl:if test="location">
+          <xsl:apply-templates select="location/@*|location/node()"/>
+        </xsl:if>
+      </div>
+    </div>
+  </xsl:template>
+  <xsl:template name="emitPrevTopic">
+    <xsl:param name="start"/>
+    <xsl:variable name="prev" select="$start/preceding-sibling::topic[position()=1]"/>
+    <xsl:variable name="parentLastChild" select="$prev"/>
+    <xsl:variable name="parent" select="$start/parent::topic"/>
+    <xsl:choose>
+      <!-- if there's a previous sibling, use "last deepest child" algorithm -->
+      <xsl:when test="$prev/topic">
+        <xsl:call-template name="emitDeepestPriorChild">
+          <xsl:with-param name="start" select="$prev"/>
+        </xsl:call-template>
+      </xsl:when>
+      <!-- if there's a previous sibling without a child, use it -->
+      <xsl:when test="$prev">
+        <nobr>
+          <a href="{$prev/id}.html">&#171; prev</a>
+        </nobr>
+      </xsl:when>
+      <!-- if there's a parent, use it -->
+      <xsl:when test="$parent">
+        <nobr>
+          <a href="{$parent/id}.html">&#171; prev</a>
+        </nobr>
+      </xsl:when>
+      <xsl:otherwise>
+        <nobr style="visibility:hidden">&#171; prev</nobr>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  <xsl:template name="emitDeepestPriorChild">
+    <xsl:param name="start"/>
+    <xsl:choose>
+      <xsl:when test="$start/topic">
+        <xsl:call-template name="emitDeepestPriorChild">
+          <xsl:with-param name="start" select="$start/topic[position()=last()]"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <nobr>
+          <a href="{$start/id}.html">&#171; prev</a>
+        </nobr>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  <xsl:template name="emitNextTopic">
+    <xsl:param name="start"/>
+    <xsl:variable name="child" select="$start/topic[position()=1]"/>
+    <xsl:variable name="next" select="$start/following-sibling::topic[position()=1]"/>
+    <xsl:variable name="parentNext" select="$start/ancestor::topic[following-sibling::topic]/following-sibling::topic"/>
+    <xsl:choose>
+      <!-- if there's a first child, use it -->
+      <xsl:when test="$child">
+        <nobr>
+          <a href="{$child/id}.html">next &#187;</a>
+        </nobr>
+      </xsl:when>
+      <!-- if there's a next sibling, use it -->
+      <xsl:when test="$next">
+        <nobr>
+          <a href="{$next/id}.html">next &#187;</a>
+        </nobr>
+      </xsl:when>
+      <!-- find the first parent that has a next sibling -->
+      <xsl:when test="$parentNext">
+        <nobr>
+          <a href="{$parentNext/id}.html">next &#187;</a>
+        </nobr>
+      </xsl:when>
+      <xsl:otherwise>
+        <nobr style="visibility:hidden">next &#187;</nobr>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+  <!-- Only topics that have titles get pulled in -->
+  <xsl:template match="topic[title]">
+@FILE <xsl:value-of select="id"/>.html
+		<html xmlns="http://www.w3.org/1999/xhtml">
+      <head>
+        <title>
+					Google Web Toolkit 
+					<xsl:if test="title">
+						- <xsl:value-of select="title"/>
+          </xsl:if>
+        </title>
+        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+        <link href="../css/base.css" rel="stylesheet" type="text/css"/>
+        <link href="./doc.css" rel="stylesheet" type="text/css"/>
+        <link href="../css/print.css" rel="stylesheet" media="print" type="text/css"/>
+      </head>
+      <body>
+        <xsl:call-template name="gen-header"/>
+        <xsl:call-template name="gen-side"/>
+        <div id="body">
+          <!-- If there is a header, prefer it to the title -->
+          <xsl:choose>
+            <xsl:when test="header">
+              <xsl:apply-templates select="header/@*|header/node()"/>
+            </xsl:when>
+            <xsl:otherwise>
+              <!-- The title for this topic -->
+              <h1>
+                <xsl:value-of select="title"/>
+              </h1>
+            </xsl:otherwise>
+          </xsl:choose>
+          <!-- The topic body -->
+          <xsl:if test="body">
+            <xsl:apply-templates select="body/@*|body/node()"/>
+          </xsl:if>
+          <!-- Tips -->
+          <xsl:for-each select="tip">
+            <div class="tipContainer">
+              <div class="tipCallout">
+							   Tip
+							</div>
+              <div class="tipBody">
+                <xsl:apply-templates select="node()"/>
+              </div>
+            </div>
+          </xsl:for-each>
+          <!-- Links to child topics -->
+          <xsl:if test="childIntro">
+            <xsl:if test="childIntro/text()">
+              <h2>
+                <xsl:value-of select="childIntro/text()"/>
+              </h2>
+            </xsl:if>
+            <ul>
+              <xsl:for-each select="topic">
+                <li>
+                  <div class="heading">
+                    <xsl:call-template name="makeLink">
+                      <xsl:with-param name="linkText" select="title/node()"/>
+                      <xsl:with-param name="linkTarget" select="."/>
+                    </xsl:call-template>
+                  </div>
+                  <xsl:if test="synopsis">
+                    <div>
+                      <xsl:apply-templates select="synopsis"/>
+                    </div>
+                  </xsl:if>
+                </li>
+              </xsl:for-each>
+            </ul>
+          </xsl:if>
+          <!-- See also links -->
+          <xsl:if test="seeAlso/link">
+            <div class="topicSeeAlso">
+              <h2>Related topics</h2>
+              <xsl:for-each select="seeAlso/link">
+                <xsl:apply-templates select="."/>
+                <xsl:if test="position()!=last()">, </xsl:if>
+              </xsl:for-each>
+            </div>
+          </xsl:if>
+        </div>
+        <div id="footer">
+      &#169;2006 Google
+      <span class="noprint"> -
+        <a href="http://www.google.com/">Google Home</a> -
+        <a href="http://www.google.com/jobs/">We're Hiring</a> -
+        <a href="http://www.google.com/privacy.html">Privacy Policy</a> -
+        <a href="http://www.google.com/terms_of_service.html">Terms of Service</a> -
+        <a href="mailto:code@google.com">Contact Us</a>
+          </span>
+        </div>
+        <!-- analytics -->
+        <script src="https://ssl.google-analytics.com/urchin.js" type="text/javascript"/>
+        <script type="text/javascript">_uacct="UA-18071-1"; _uanchor=1; urchinTracker();</script>
+      </body>
+    </html>
+  </xsl:template>
+  <xsl:template match="@*|node()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+  <xsl:template match="link">
+    <xsl:variable name="testLinkBase" select="substring-before(@ref, &quot;#&quot;)"/>
+    <xsl:variable name="linkBase">
+      <xsl:if test="string-length($testLinkBase) != 0">
+        <xsl:value-of select="$testLinkBase"/>
+      </xsl:if>
+      <xsl:if test="string-length($testLinkBase) = 0">
+        <xsl:value-of select="@ref"/>
+      </xsl:if>
+    </xsl:variable>
+    <xsl:call-template name="makeLink">
+      <xsl:with-param name="linkRef" select="@ref"/>
+      <xsl:with-param name="linkText" select="node()"/>
+      <xsl:with-param name="linkTarget" select="key('topicSearch', $linkBase)"/>
+    </xsl:call-template>
+  </xsl:template>
+  <xsl:template name="makeLink">
+    <!-- This only becomes a hyperlink if the cross-ref can be resolved -->
+    <xsl:param name="linkRef"/>
+    <xsl:param name="linkText"/>
+    <xsl:param name="linkTarget"/>
+    <xsl:choose>
+      <xsl:when test="$linkTarget">
+        <xsl:variable name="extra" select="substring-after($linkRef, &quot;#&quot;)"/>
+        <xsl:element name="a">
+          <xsl:attribute name="href"><xsl:value-of select="$linkTarget/id"/><xsl:text>.html</xsl:text><xsl:if test="string-length($extra) != 0"><xsl:text>#</xsl:text><xsl:value-of select="$extra"/></xsl:if></xsl:attribute>
+          <xsl:apply-templates select="$linkText"/>
+        </xsl:element>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- plain text -->
+        <xsl:apply-templates select="$linkText"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-index.xslt b/build-tools/doctool/src/topics-index.xslt
new file mode 100644
index 0000000..0620bc8
--- /dev/null
+++ b/build-tools/doctool/src/topics-index.xslt
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+	<xsl:output method="html"/>
+	<xsl:param name="title"/>
+	<xsl:template match="/">
+		<html>
+			<head>
+				<link rel="stylesheet" href="help.css" type="text/css"/>
+			</head>
+			<body style="margin: 0.5em">
+				<h1>
+					<xsl:value-of select="$title"/>
+				</h1>
+				<xsl:apply-templates select="//topic[index]">
+					<xsl:sort select="index" order="ascending"/>
+				</xsl:apply-templates>
+			</body>
+		</html>
+	</xsl:template>
+	<xsl:template match="topic[index]">
+		<div>
+			<xsl:value-of select="index"/>
+			<xsl:text> - </xsl:text>
+			<xsl:element name="a">
+				<xsl:attribute name="href"><xsl:value-of select="id"/>.html</xsl:attribute>
+				<xsl:attribute name="class">tocLink</xsl:attribute>
+				<xsl:value-of select="title"/>
+			</xsl:element>
+		</div>
+	</xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-singlehtml.xslt b/build-tools/doctool/src/topics-singlehtml.xslt
new file mode 100644
index 0000000..08e64ba
--- /dev/null
+++ b/build-tools/doctool/src/topics-singlehtml.xslt
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html"/>
+    <xsl:strip-space elements="*"/>
+
+    <xsl:key name="topicSearch" match="//topic" use="id"/>
+
+    <xsl:param name="css"/>
+
+    <xsl:template match="/">
+        <html>
+            <head>
+                <title>Innuvo Dynamic Client System 1.1 Documentation</title>
+                <link rel="stylesheet" href="help.css" type="text/css" />
+            </head>
+            <body>
+                <xsl:apply-templates select="//topic"/> 
+            </body>
+        </html>
+    </xsl:template>
+
+    <!-- Only topics that have titles get pulled in -->
+    <xsl:template match="topic[title]">
+        <xsl:element name="a">
+            <xsl:attribute name="name"><xsl:value-of select="id"/></xsl:attribute>
+        </xsl:element>
+
+<!--
+        <xsl:if test="header">
+            <div class="topicHeader">
+                <xsl:apply-templates select="header/@*|header/node()" />
+            </div>
+        </xsl:if>
+-->
+
+        <xsl:if test="body">
+            <div class="topicBody">
+                <xsl:apply-templates select="body/@*|body/node()" />
+            </div>
+        </xsl:if>
+        
+        <!-- Links to child topics -->
+<!--        
+        <xsl:if test="childIntro">
+            <div class="topicChildren">
+                <xsl:apply-templates select="childIntro"/>
+                <ul>
+                    <xsl:for-each select="topic">
+                        <li>
+                            <xsl:call-template name="makeLink">
+                                <xsl:with-param name="linkText" select="title/node()"/>
+                                <xsl:with-param name="linkTarget" select="."/>
+                            </xsl:call-template>
+                            <xsl:if test="synopsis">
+                                - <xsl:apply-templates select="synopsis"/>
+                            </xsl:if>
+                        </li>
+                    </xsl:for-each>
+                </ul>
+            </div>
+        </xsl:if>
+-->
+
+        <!-- See also links -->
+<!--        
+        <xsl:if test="seeAlso">
+            <div class="topicSeeAlso">
+                <h2>Related topics</h2>
+                <xsl:for-each select="seeAlso">
+                    <xsl:apply-templates select="link"/>
+                    <xsl:if test="position()!=last()">, </xsl:if> 
+                </xsl:for-each>
+            </div>
+        </xsl:if>
+-->        
+    </xsl:template>
+
+    <xsl:template match="@*|node()">
+        <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
+    </xsl:template>
+    
+    <xsl:template match="pre">
+        <!-- Do not copy, wastes time during spelling/grammar checks -->
+        <div>CODE SAMPLE REMOVED</div>
+    </xsl:template>
+    
+    <xsl:template match="table[@class='members']">
+        <!-- Do not copy, wastes time during spelling/grammar checks -->
+        <div>TABLE OF MEMBERS REMOVED</div>
+    </xsl:template>
+
+    <xsl:template match="img">
+        <!-- Do not copy, wastes time during spelling/grammar checks -->
+        <div>IMAGE REMOVED</div>
+    </xsl:template>
+
+    <xsl:template match="link">
+        <xsl:call-template name="makeLink">
+            <xsl:with-param name="linkText" select="node()"/>
+            <xsl:with-param name="linkTarget" select="key('topicSearch', @ref)"/>
+        </xsl:call-template>
+    </xsl:template>
+    
+    <xsl:template name="makeLink">
+        <xsl:param name="linkText"/>
+        <xsl:param name="linkTarget"/>
+        <!-- Always emit plain text -->
+        <xsl:apply-templates select="$linkText"/>
+    </xsl:template>
+    
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-toc.xslt b/build-tools/doctool/src/topics-toc.xslt
new file mode 100644
index 0000000..70d285e
--- /dev/null
+++ b/build-tools/doctool/src/topics-toc.xslt
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+	<xsl:output method="html"/>
+	<xsl:param name="title"/>
+	<xsl:template match="/">
+		<html>
+			<head>
+				<link rel="stylesheet" href="help.css" type="text/css"/>
+			</head>
+			<body style="margin: 0.5em">
+				<h1>
+					<xsl:value-of select="$title"/>
+				</h1>
+				<xsl:apply-templates select="/topics/topic">
+					<xsl:sort select="order" data-type="number" order="descending"/>
+				</xsl:apply-templates>
+			</body>
+		</html>
+	</xsl:template>
+
+	<xsl:template match="topic">
+		<div style="margin-left: 2%">
+			<xsl:element name="a">
+				<xsl:attribute name="href"><xsl:value-of select="id"/>.html</xsl:attribute>
+				<xsl:attribute name="class">tocLink</xsl:attribute>
+				<xsl:value-of select="title"/>
+			</xsl:element>
+			<xsl:apply-templates select="topic"/>
+		</div>
+	</xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics.xsd b/build-tools/doctool/src/topics.xsd
new file mode 100644
index 0000000..7f8342f
--- /dev/null
+++ b/build-tools/doctool/src/topics.xsd
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
+	<xs:element name="topics">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element ref="topic" minOccurs="0" maxOccurs="unbounded"/>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="topic">
+		<xs:annotation>
+			<xs:documentation>Topic element; can be freely nested</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:sequence minOccurs="0">
+					<xs:element name="id" type="xs:string">
+						<xs:annotation>
+							<xs:documentation>The id for this topic, which should be unique for the entire library</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="order" type="xs:int" minOccurs="0">
+						<xs:annotation>
+							<xs:documentation>An arbitrary number used to indicate sorting order</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="title" type="xs:string">
+						<xs:annotation>
+							<xs:documentation>A simple title for the topic</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="index" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+						<xs:annotation>
+							<xs:documentation>A word or phrase to include in the documentation index that points to this topic</xs:documentation>
+						</xs:annotation>
+					</xs:element>
+					<xs:element name="synopsis" minOccurs="0">
+						<xs:annotation>
+							<xs:documentation>A short (one phrase or sentence) that summaries the point of the topic</xs:documentation>
+						</xs:annotation>
+						<xs:complexType mixed="true">
+							<xs:sequence>
+								<xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+							</xs:sequence>
+						</xs:complexType>
+					</xs:element>
+					<xs:element name="header">
+						<xs:annotation>
+							<xs:documentation>HTML header that may get special treatment in the published documentation</xs:documentation>
+						</xs:annotation>
+						<xs:complexType mixed="true">
+							<xs:sequence>
+								<xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+							</xs:sequence>
+						</xs:complexType>
+					</xs:element>
+					<xs:element name="body">
+						<xs:annotation>
+							<xs:documentation>HTML body that constitutes the primary content of the topic</xs:documentation>
+						</xs:annotation>
+						<xs:complexType mixed="true">
+							<xs:sequence>
+								<xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+							</xs:sequence>
+						</xs:complexType>
+					</xs:element>
+					<xs:element name="seeAlso" minOccurs="0" maxOccurs="unbounded">
+						<xs:annotation>
+							<xs:documentation>A list of related cross-ref links</xs:documentation>
+						</xs:annotation>
+						<xs:complexType>
+							<xs:sequence>
+								<xs:element name="link" minOccurs="0" maxOccurs="unbounded">
+									<xs:annotation>
+										<xs:documentation>Links to topics that are not necessarily child topics</xs:documentation>
+									</xs:annotation>
+									<xs:complexType mixed="true">
+										<xs:sequence minOccurs="0" maxOccurs="unbounded">
+											<xs:any namespace="##any" processContents="skip">
+												<xs:annotation>
+													<xs:documentation>HTML representing the link caption</xs:documentation>
+												</xs:annotation>
+											</xs:any>
+										</xs:sequence>
+										<xs:attribute name="ref">
+											<xs:annotation>
+												<xs:documentation>The target id of a topic anywhere in the library; if absent, it a link that could not be resolved</xs:documentation>
+											</xs:annotation>
+										</xs:attribute>
+									</xs:complexType>
+								</xs:element>
+							</xs:sequence>
+						</xs:complexType>
+					</xs:element>
+				</xs:sequence>
+				<xs:sequence>
+					<xs:element name="childIntro" minOccurs="0"/>
+					<xs:element ref="topic" minOccurs="0" maxOccurs="unbounded"/>
+				</xs:sequence>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+</xs:schema>