auto import from //depot/cupcake/@135843
diff --git a/tools/droiddoc/src/Android.mk b/tools/droiddoc/src/Android.mk
new file mode 100644
index 0000000..abd2581
--- /dev/null
+++ b/tools/droiddoc/src/Android.mk
@@ -0,0 +1,69 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := docs
+
+LOCAL_SRC_FILES := \
+    AnnotationInstanceInfo.java \
+    AnnotationValueInfo.java \
+	AttributeInfo.java \
+	AttrTagInfo.java \
+	ClassInfo.java \
+	DroidDoc.java \
+	ClearPage.java \
+	Comment.java \
+	ContainerInfo.java \
+	Converter.java \
+	DocFile.java \
+	DocInfo.java \
+	Errors.java \
+	FieldInfo.java \
+	Hierarchy.java \
+	InheritedTags.java \
+	KeywordEntry.java \
+    LinkReference.java \
+	LiteralTagInfo.java \
+	MemberInfo.java \
+	MethodInfo.java \
+	NavTree.java \
+	PackageInfo.java \
+	ParamTagInfo.java \
+	ParameterInfo.java \
+	ParsedTagInfo.java \
+	Proofread.java \
+	SampleCode.java \
+	SampleTagInfo.java \
+    Scoped.java \
+	SeeTagInfo.java \
+	Sorter.java \
+	SourcePositionInfo.java \
+    Stubs.java \
+	TagInfo.java \
+    TextTagInfo.java \
+	ThrowsTagInfo.java \
+	TodoFile.java \
+	TypeInfo.java
+
+LOCAL_JAVA_LIBRARIES := \
+	clearsilver
+
+LOCAL_CLASSPATH := \
+	$(HOST_JDK_TOOLS_JAR)
+
+LOCAL_MODULE:= droiddoc
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/droiddoc/src/AnnotationInstanceInfo.java b/tools/droiddoc/src/AnnotationInstanceInfo.java
new file mode 100644
index 0000000..07d4aa3
--- /dev/null
+++ b/tools/droiddoc/src/AnnotationInstanceInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class AnnotationInstanceInfo
+{
+    private ClassInfo mType;
+    private AnnotationValueInfo[] mElementValues;
+
+    public AnnotationInstanceInfo(ClassInfo type, AnnotationValueInfo[] elementValues)
+    {
+        mType = type;
+        mElementValues = elementValues;
+    }
+
+    ClassInfo type()
+    {
+        return mType;
+    }
+
+    AnnotationValueInfo[] elementValues()
+    {
+        return mElementValues;
+    }
+
+    public String toString()
+    {
+        StringBuilder str = new StringBuilder();
+        str.append("@");
+        str.append(mType.qualifiedName());
+        str.append("(");
+        AnnotationValueInfo[] values = mElementValues;
+        final int N = values.length;
+        for (int i=0; i<N; i++) {
+            AnnotationValueInfo value = values[i];
+            str.append(value.element().name());
+            str.append("=");
+            str.append(value.valueString());
+            if (i != N-1) {
+                str.append(",");
+            }
+        }
+        str.append(")");
+        return str.toString();
+    }
+}
+
diff --git a/tools/droiddoc/src/AnnotationValueInfo.java b/tools/droiddoc/src/AnnotationValueInfo.java
new file mode 100644
index 0000000..a2d869a
--- /dev/null
+++ b/tools/droiddoc/src/AnnotationValueInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class AnnotationValueInfo
+{
+    private Object mValue;
+    private String mString;
+    private MethodInfo mElement;
+
+    public AnnotationValueInfo(MethodInfo element)
+    {
+        mElement = element;
+    }
+
+    public void init(Object value)
+    {
+        mValue = value;
+    }
+
+    public MethodInfo element()
+    {
+        return mElement;
+    }
+
+    public Object value()
+    {
+        return mValue;
+    }
+
+    public String valueString()
+    {
+        Object v = mValue;
+        if (v instanceof TypeInfo) {
+            return ((TypeInfo)v).fullName();
+        }
+        else if (v instanceof FieldInfo) {
+            StringBuilder str = new StringBuilder();
+            FieldInfo f = (FieldInfo)v;
+            str.append(f.containingClass().qualifiedName());
+            str.append('.');
+            str.append(f.name());
+            return str.toString();
+        }
+        else if (v instanceof AnnotationInstanceInfo) {
+            return v.toString();
+        }
+        else if (v instanceof AnnotationValueInfo[]) {
+            StringBuilder str = new StringBuilder();
+            AnnotationValueInfo[] array = (AnnotationValueInfo[])v;
+            final int N = array.length;
+            str.append("{");
+            for (int i=0; i<array.length; i++) {
+                str.append(array[i].valueString());
+                if (i != N-1) {
+                    str.append(",");
+                }
+            }
+            str.append("}");
+            return str.toString();
+        }
+        else {
+            return FieldInfo.constantLiteralValue(v);
+        }
+    }
+}
+
diff --git a/tools/droiddoc/src/AttrTagInfo.java b/tools/droiddoc/src/AttrTagInfo.java
new file mode 100644
index 0000000..abc5452
--- /dev/null
+++ b/tools/droiddoc/src/AttrTagInfo.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+
+public class AttrTagInfo extends TagInfo
+{
+    private static final String REF_COMMAND = "ref";
+    private static final String NAME_COMMAND = "name";
+    private static final String DESCRIPTION_COMMAND = "description";
+    private static final Pattern TEXT = Pattern.compile("(\\S+)\\s*(.*)", Pattern.DOTALL);
+    private static final Pattern NAME_TEXT = Pattern.compile("(\\S+)(.*)",
+                                                Pattern.DOTALL);
+
+    private ContainerInfo mBase;
+    private String mCommand;
+
+    // if mCommand == "ref"
+    private FieldInfo mRefField;
+    private AttributeInfo mAttrInfo;
+
+    // if mCommand == "name"
+    private String mAttrName;
+
+    // if mCommand == "description"
+    private Comment mDescrComment;
+
+    AttrTagInfo(String name, String kind, String text, ContainerInfo base,
+            SourcePositionInfo position)
+    {
+        super(name, kind, text, position);
+        mBase = base;
+
+        parse(text, base, position);
+    }
+
+    void parse(String text, ContainerInfo base, SourcePositionInfo position) {
+        Matcher m;
+
+        m = TEXT.matcher(text);
+        if (!m.matches()) {
+            Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr tag: " + text);
+            return;
+        }
+
+        String command = m.group(1);
+        String more = m.group(2);
+
+        if (REF_COMMAND.equals(command)) {
+            String ref = more.trim();
+            LinkReference linkRef = LinkReference.parse(ref, mBase, position, false);
+            if (!linkRef.good) {
+                Errors.error(Errors.BAD_ATTR_TAG, position, "Unresolved @attr ref: " + ref);
+                return;
+            }
+            if (!(linkRef.memberInfo instanceof FieldInfo)) {
+                Errors.error(Errors.BAD_ATTR_TAG, position, "@attr must be a field: " + ref);
+                return;
+            }
+            mCommand = command;
+            mRefField = (FieldInfo)linkRef.memberInfo;
+        }
+        else if (NAME_COMMAND.equals(command)) {
+            m = NAME_TEXT.matcher(more);
+            if (!m.matches() || m.group(2).trim().length() != 0) {
+                Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr name tag: " + more);
+                return;
+            }
+            mCommand = command;
+            mAttrName = m.group(1);
+        }
+        else if (DESCRIPTION_COMMAND.equals(command)) {
+            mCommand = command;
+            mDescrComment = new Comment(more, base, position);
+        }
+        else {
+            Errors.error(Errors.BAD_ATTR_TAG, position, "Bad @attr command: " + command);
+        }
+    }
+
+    public FieldInfo reference() {
+        return REF_COMMAND.equals(mCommand) ? mRefField : null;
+    }
+    
+    public String name() {
+        return NAME_COMMAND.equals(mCommand) ? mAttrName : null;
+    }
+
+    public Comment description() {
+        return DESCRIPTION_COMMAND.equals(mCommand) ? mDescrComment : null;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        super.makeHDF(data, base);
+    }
+
+    public void setAttribute(AttributeInfo info) {
+        mAttrInfo = info;
+    }
+
+    public static void makeReferenceHDF(HDF data, String base, AttrTagInfo[] tags)
+    {
+        int i=0;
+        for (AttrTagInfo t: tags) {
+            if (REF_COMMAND.equals(t.mCommand)) {
+                if (t.mAttrInfo == null) {
+                    String msg = "ERROR: unlinked attr: " + t.mRefField.name();
+                    if (false) {
+                        System.out.println(msg);
+                    } else {
+                        throw new RuntimeException(msg);
+                    }
+                } else {
+                    data.setValue(base + "." + i + ".name", t.mAttrInfo.name());
+                    data.setValue(base + "." + i + ".href", t.mAttrInfo.htmlPage());
+                    i++;
+                }
+            }
+        }
+    }
+
+}
diff --git a/tools/droiddoc/src/AttributeInfo.java b/tools/droiddoc/src/AttributeInfo.java
new file mode 100644
index 0000000..a24106b
--- /dev/null
+++ b/tools/droiddoc/src/AttributeInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Comparator;
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+public class AttributeInfo {
+    public static final Comparator<AttributeInfo> comparator = new Comparator<AttributeInfo>() {
+        public int compare(AttributeInfo a, AttributeInfo b) {
+            return a.name().compareTo(b.name());
+        }
+    };
+    
+    public FieldInfo attrField;
+    public ArrayList<MethodInfo> methods = new ArrayList<MethodInfo>();
+    
+    private ClassInfo mClass;
+    private String mName;
+    private Comment mComment;
+
+    public AttributeInfo(ClassInfo cl, FieldInfo f) {
+        mClass = cl;
+        attrField = f;
+    }
+
+    public String name() {
+        if (mName == null) {
+            for (AttrTagInfo comment: attrField.comment().attrTags()) {
+                String n = comment.name();
+                if (n != null) {
+                    mName = n;
+                    return n;
+                }
+            }
+        }
+        return mName;
+    }
+
+    public Comment comment() {
+        if (mComment == null) {
+            for (AttrTagInfo attr: attrField.comment().attrTags()) {
+                Comment c = attr.description();
+                if (c != null) {
+                    mComment = c;
+                    return c;
+                }
+            }
+        }
+        if (mComment == null) {
+            return new Comment("", mClass, new SourcePositionInfo());
+        }
+        return mComment;
+    }
+    
+    public String anchor() {
+        return "attr_" + name();
+    }
+    public String htmlPage() {
+        return mClass.htmlPage() + "#" + anchor();
+    }
+
+    public void makeHDF(HDF data, String base) {
+        data.setValue(base + ".name", name());
+        data.setValue(base + ".anchor", anchor());
+        data.setValue(base + ".href", htmlPage());
+        data.setValue(base + ".R.name", attrField.name());
+        data.setValue(base + ".R.href", attrField.htmlPage());
+        TagInfo.makeHDF(data, base + ".deprecated", attrField.comment().deprecatedTags());
+        TagInfo.makeHDF(data, base + ".shortDescr", comment().briefTags());
+        TagInfo.makeHDF(data, base + ".descr", comment().tags());
+
+        int i=0;
+        for (MethodInfo m: methods) {
+            String s = base + ".methods." + i;
+            data.setValue(s + ".href", m.htmlPage());
+            data.setValue(s + ".name", m.name() + m.prettySignature());
+        }
+    }
+
+    public boolean checkLevel() {
+        return attrField.checkLevel();
+    }
+}
+
diff --git a/tools/droiddoc/src/ClassInfo.java b/tools/droiddoc/src/ClassInfo.java
new file mode 100644
index 0000000..36edbf8
--- /dev/null
+++ b/tools/droiddoc/src/ClassInfo.java
@@ -0,0 +1,1463 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.sun.javadoc.*;
+import com.sun.tools.doclets.*;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Scoped
+{
+    public static final Comparator<ClassInfo> comparator = new Comparator<ClassInfo>() {
+        public int compare(ClassInfo a, ClassInfo b) {
+            return a.name().compareTo(b.name());
+        }
+    };
+
+    public static final Comparator<ClassInfo> qualifiedComparator = new Comparator<ClassInfo>() {
+        public int compare(ClassInfo a, ClassInfo b) {
+            return a.qualifiedName().compareTo(b.qualifiedName());
+        }
+    };
+
+    public ClassInfo(
+            ClassDoc cl,
+            String rawCommentText, SourcePositionInfo position,
+            boolean isPublic, boolean isProtected, boolean isPackagePrivate,
+            boolean isPrivate, boolean isStatic,
+            boolean isInterface, boolean isAbstract, boolean isOrdinaryClass,
+            boolean isException, boolean isError, boolean isEnum, boolean isAnnotation,
+            boolean isFinal, boolean isIncluded, String name,
+            String qualifiedName, String qualifiedTypeName, boolean isPrimitive)
+    {
+        super(rawCommentText, position);
+
+        mClass = cl;
+        mIsPublic = isPublic;
+        mIsProtected = isProtected;
+        mIsPackagePrivate = isPackagePrivate;
+        mIsPrivate = isPrivate;
+        mIsStatic = isStatic;
+        mIsInterface = isInterface;
+        mIsAbstract = isAbstract;
+        mIsOrdinaryClass = isOrdinaryClass;
+        mIsException = isException;
+        mIsError = isError;
+        mIsEnum = isEnum;
+        mIsAnnotation = isAnnotation;
+        mIsFinal = isFinal;
+        mIsIncluded = isIncluded;
+        mName = name;
+        mQualifiedName = qualifiedName;
+        mQualifiedTypeName = qualifiedTypeName;
+        mIsPrimitive = isPrimitive;
+        mNameParts = name.split("\\.");
+    }
+
+    public void init(TypeInfo typeInfo, ClassInfo[] interfaces, TypeInfo[] interfaceTypes,
+            ClassInfo[] innerClasses,
+            MethodInfo[] constructors, MethodInfo[] methods, MethodInfo[] annotationElements,
+            FieldInfo[] fields, FieldInfo[] enumConstants,
+            PackageInfo containingPackage, ClassInfo containingClass,
+            ClassInfo superclass, TypeInfo superclassType, AnnotationInstanceInfo[] annotations)
+    {
+        mTypeInfo = typeInfo;
+        mRealInterfaces = interfaces;
+        mRealInterfaceTypes = interfaceTypes;
+        mInnerClasses = innerClasses;
+        mAllConstructors = constructors;
+        mAllSelfMethods = methods;
+        mAnnotationElements = annotationElements;
+        mAllSelfFields = fields;
+        mEnumConstants = enumConstants;
+        mContainingPackage = containingPackage;
+        mContainingClass = containingClass;
+        mRealSuperclass = superclass;
+        mRealSuperclassType = superclassType;
+        mAnnotations = annotations;
+
+        // after providing new methods and new superclass info,clear any cached
+        // lists of self + superclass methods, ctors, etc.
+        mSuperclassInit = false;
+        mConstructors = null;
+        mMethods = null;
+        mSelfMethods = null;
+        mFields = null;
+        mSelfFields = null;
+        mSelfAttributes = null;
+        mDeprecatedKnown = false;
+        
+        Arrays.sort(mEnumConstants, FieldInfo.comparator);
+        Arrays.sort(mInnerClasses, ClassInfo.comparator);
+    }
+
+    public void init2() {
+        // calling this here forces the AttrTagInfo objects to be linked to the AttribtueInfo
+        // objects
+        selfAttributes();
+    }
+    
+    public void init3(TypeInfo[] types, ClassInfo[] realInnerClasses){
+      mTypeParameters = types;
+      mRealInnerClasses = realInnerClasses;
+    }
+    
+    public ClassInfo[] getRealInnerClasses(){
+      return mRealInnerClasses;
+    }
+    
+    public TypeInfo[] getTypeParameters(){
+      return mTypeParameters;
+    }
+
+    public boolean checkLevel()
+    {
+        int val = mCheckLevel;
+        if (val >= 0) {
+            return val != 0;
+        } else {
+            boolean v = DroidDoc.checkLevel(mIsPublic, mIsProtected,
+                                                mIsPackagePrivate, mIsPrivate, isHidden());
+            mCheckLevel = v ? 1 : 0;
+            return v;
+        }
+    }
+
+    public int compareTo(Object that) {
+        if (that instanceof ClassInfo) {
+            return mQualifiedName.compareTo(((ClassInfo)that).mQualifiedName);
+        } else {
+            return this.hashCode() - that.hashCode();
+        }
+    }
+
+    public ContainerInfo parent()
+    {
+        return this;
+    }
+
+    public boolean isPublic()
+    {
+        return mIsPublic;
+    }
+
+    public boolean isProtected()
+    {
+        return mIsProtected;
+    }
+
+    public boolean isPackagePrivate()
+    {
+        return mIsPackagePrivate;
+    }
+
+    public boolean isPrivate()
+    {
+        return mIsPrivate;
+    }
+
+    public boolean isStatic()
+    {
+        return mIsStatic;
+    }
+
+    public boolean isInterface()
+    {
+        return mIsInterface;
+    }
+
+    public boolean isAbstract()
+    {
+        return mIsAbstract;
+    }
+
+    public PackageInfo containingPackage()
+    {
+        return mContainingPackage;
+    }
+
+    public ClassInfo containingClass()
+    {
+        return mContainingClass;
+    }
+
+    public boolean isOrdinaryClass()
+    {
+        return mIsOrdinaryClass;
+    }
+
+    public boolean isException()
+    {
+        return mIsException;
+    }
+
+    public boolean isError()
+    {
+        return mIsError;
+    }
+
+    public boolean isEnum()
+    {
+        return mIsEnum;
+    }
+
+    public boolean isAnnotation()
+    {
+        return mIsAnnotation;
+    }
+
+    public boolean isFinal()
+    {
+        return mIsFinal;
+    }
+
+    public boolean isIncluded()
+    {
+        return mIsIncluded;
+    }
+
+    public HashSet<String> typeVariables()
+    {
+        HashSet<String> result = TypeInfo.typeVariables(mTypeInfo.typeArguments());
+        ClassInfo cl = containingClass();
+        while (cl != null) {
+            TypeInfo[] types = cl.asTypeInfo().typeArguments();
+            if (types != null) {
+                TypeInfo.typeVariables(types, result);
+            }
+            cl = cl.containingClass();
+        }
+        return result;
+    }
+
+    private static void gatherHiddenInterfaces(ClassInfo cl, HashSet<ClassInfo> interfaces) {
+        for (ClassInfo iface: cl.mRealInterfaces) {
+            if (iface.checkLevel()) {
+                interfaces.add(iface);
+            } else {
+                gatherHiddenInterfaces(iface, interfaces);
+            }
+        }
+    }
+
+    public ClassInfo[] interfaces()
+    {
+        if (mInterfaces == null) {
+            if (checkLevel()) {
+                HashSet<ClassInfo> interfaces = new HashSet<ClassInfo>();
+                ClassInfo superclass = mRealSuperclass;
+                while (superclass != null && !superclass.checkLevel()) {
+                    gatherHiddenInterfaces(superclass, interfaces);
+                    superclass = superclass.mRealSuperclass;
+                }
+                gatherHiddenInterfaces(this, interfaces);
+                mInterfaces = interfaces.toArray(new ClassInfo[interfaces.size()]);
+            } else {
+                // put something here in case someone uses it
+                mInterfaces = mRealInterfaces;
+            }
+            Arrays.sort(mInterfaces, ClassInfo.qualifiedComparator);
+        }
+        return mInterfaces;
+    }
+
+    public ClassInfo[] realInterfaces()
+    {
+        return mRealInterfaces;
+    }
+
+    TypeInfo[] realInterfaceTypes()
+    {
+        return mRealInterfaceTypes;
+    }
+
+    public String name()
+    {
+        return mName;
+    }
+
+    public String[] nameParts()
+    {
+        return mNameParts;
+    }
+
+    public String leafName()
+    {
+        return mNameParts[mNameParts.length-1];
+    }
+
+    public String qualifiedName()
+    {
+        return mQualifiedName;
+    }
+
+    public String qualifiedTypeName()
+    {
+        return mQualifiedTypeName;
+    }
+
+    public boolean isPrimitive()
+    {
+        return mIsPrimitive;
+    }
+
+    public MethodInfo[] allConstructors() {
+        return mAllConstructors;
+    }
+
+    public MethodInfo[] constructors()
+    {
+        if (mConstructors == null) {
+            MethodInfo[] methods = mAllConstructors;
+            ArrayList<MethodInfo> ctors = new ArrayList<MethodInfo>();
+            for (int i=0; i<methods.length; i++) {
+                MethodInfo m = methods[i];
+                if (!m.isHidden()) {
+                    ctors.add(m);
+                }
+            }
+            mConstructors = ctors.toArray(new MethodInfo[ctors.size()]);
+            Arrays.sort(mConstructors, MethodInfo.comparator);
+        }
+        return mConstructors;
+    }
+
+    public ClassInfo[] innerClasses()
+    {
+        return mInnerClasses;
+    }
+
+    public TagInfo[] inlineTags()
+    {
+        return comment().tags();
+    }
+
+    public TagInfo[] firstSentenceTags()
+    {
+        return comment().briefTags();
+    }
+    
+    public boolean isDeprecated() {
+        boolean deprecated = false;
+        if (!mDeprecatedKnown) {
+            boolean commentDeprecated = (comment().deprecatedTags().length > 0);
+            boolean annotationDeprecated = false;
+            for (AnnotationInstanceInfo annotation : annotations()) {
+                if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
+                    annotationDeprecated = true;
+                    break;
+                }
+            }
+
+            if (commentDeprecated != annotationDeprecated) {
+                Errors.error(Errors.DEPRECATION_MISMATCH, position(),
+                        "Class " + qualifiedName()
+                        + ": @Deprecated annotation and @deprecated comment do not match");
+            }
+
+            mIsDeprecated = commentDeprecated | annotationDeprecated;
+            mDeprecatedKnown = true;
+        }
+        return mIsDeprecated;
+    }
+
+    public TagInfo[] deprecatedTags()
+    {
+        TagInfo[] result = comment().deprecatedTags();
+        if (result.length == 0) {
+            if (comment().undeprecateTags().length == 0) {
+                if (superclass() != null) {
+                    result = superclass().deprecatedTags();
+                }
+            }
+        }
+        // should we also do the interfaces?
+        return result;
+    }
+
+    public MethodInfo[] methods()
+    {
+        if (mMethods == null) {
+            TreeMap<String,MethodInfo> all = new TreeMap<String,MethodInfo>();
+
+            ClassInfo[] ifaces = interfaces();
+            for (ClassInfo iface: ifaces) {
+                if (iface != null) {
+                    MethodInfo[] inhereted = iface.methods();
+                    for (MethodInfo method: inhereted) {
+                        String key = method.name() + method.signature();
+                        all.put(key, method);
+                    }
+                }
+            }
+
+            ClassInfo superclass = superclass();
+            if (superclass != null) {
+                MethodInfo[] inhereted = superclass.methods();
+                for (MethodInfo method: inhereted) {
+                    String key = method.name() + method.signature();
+                    all.put(key, method);
+                }
+            }
+
+            MethodInfo[] methods = selfMethods();
+            for (MethodInfo method: methods) {
+                String key = method.name() + method.signature();
+                MethodInfo old = all.put(key, method);
+            }
+
+            mMethods = all.values().toArray(new MethodInfo[all.size()]);
+        }
+        return mMethods;
+    }
+
+    public MethodInfo[] annotationElements()
+    {
+        return mAnnotationElements;
+    }
+
+    public AnnotationInstanceInfo[] annotations()
+    {
+        return mAnnotations;
+    }
+
+    private static void addFields(ClassInfo cl, TreeMap<String,FieldInfo> all)
+    {
+        FieldInfo[] fields = cl.fields();
+        int N = fields.length;
+        for (int i=0; i<N; i++) {
+            FieldInfo f = fields[i];
+            all.put(f.name(), f);
+        }
+    }
+
+    public FieldInfo[] fields()
+    {
+        if (mFields == null) {
+            int N;
+            TreeMap<String,FieldInfo> all = new TreeMap<String,FieldInfo>();
+
+            ClassInfo[] interfaces = interfaces();
+            N = interfaces.length;
+            for (int i=0; i<N; i++) {
+                addFields(interfaces[i], all);
+            }
+
+            ClassInfo superclass = superclass();
+            if (superclass != null) {
+                addFields(superclass, all);
+            }
+
+            FieldInfo[] fields = selfFields();
+            N = fields.length;
+            for (int i=0; i<N; i++) {
+                FieldInfo f = fields[i];
+                if (!f.isHidden()) {
+                    String key = f.name();
+                    all.put(key, f);
+                }
+            }
+
+            mFields = all.values().toArray(new FieldInfo[0]);
+        }
+        return mFields;
+    }
+
+    public void gatherFields(ClassInfo owner, ClassInfo cl, HashMap<String,FieldInfo> fields) {
+        FieldInfo[] flds = cl.selfFields();
+        for (FieldInfo f: flds) {
+            if (f.checkLevel()) {
+                fields.put(f.name(), f.cloneForClass(owner));
+            }
+        }
+    }
+
+    public FieldInfo[] selfFields()
+    {
+        if (mSelfFields == null) {
+            HashMap<String,FieldInfo> fields = new HashMap<String,FieldInfo>();
+            // our hidden parents
+            if (mRealSuperclass != null && !mRealSuperclass.checkLevel()) {
+                gatherFields(this, mRealSuperclass, fields);
+            }
+            for (ClassInfo iface: mRealInterfaces) {
+                if (!iface.checkLevel()) {
+                    gatherFields(this, iface, fields);
+                }
+            }
+            // mine
+            FieldInfo[] selfFields = mAllSelfFields;
+            for (int i=0; i<selfFields.length; i++) {
+                FieldInfo f = selfFields[i];
+                if (!f.isHidden()) {
+                    fields.put(f.name(), f);
+                }
+            }
+            // combine and return in
+            mSelfFields = fields.values().toArray(new FieldInfo[fields.size()]);
+            Arrays.sort(mSelfFields, FieldInfo.comparator);
+        }
+        return mSelfFields;
+    }
+
+    public FieldInfo[] allSelfFields() {
+        return mAllSelfFields;
+    }
+
+    public void gatherMethods(ClassInfo owner, ClassInfo cl, HashMap<String,MethodInfo> methods) {
+        MethodInfo[] meth = cl.selfMethods();
+        for (MethodInfo m: meth) {
+            if (m.checkLevel()) {
+                methods.put(m.name()+m.signature(), m.cloneForClass(owner));
+            }
+        }
+    }
+
+    public MethodInfo[] selfMethods()
+    {
+        if (mSelfMethods == null) {
+            HashMap<String,MethodInfo> methods = new HashMap<String,MethodInfo>();
+            // our hidden parents
+            if (mRealSuperclass != null && !mRealSuperclass.checkLevel()) {
+                gatherMethods(this, mRealSuperclass, methods);
+            }
+            for (ClassInfo iface: mRealInterfaces) {
+                if (!iface.checkLevel()) {
+                    gatherMethods(this, iface, methods);
+                }
+            }
+            // mine
+            MethodInfo[] selfMethods = mAllSelfMethods;
+            for (int i=0; i<selfMethods.length; i++) {
+                MethodInfo m = selfMethods[i];
+                if (m.checkLevel()) {
+                    methods.put(m.name()+m.signature(), m);
+                }
+            }
+            // combine and return it
+            mSelfMethods = methods.values().toArray(new MethodInfo[methods.size()]);
+            Arrays.sort(mSelfMethods, MethodInfo.comparator);
+        }
+        return mSelfMethods;
+    }
+
+    public MethodInfo[] allSelfMethods() {
+        return mAllSelfMethods;
+    }
+    
+    public void addMethod(MethodInfo method) {
+        MethodInfo[] methods = new MethodInfo[mAllSelfMethods.length + 1];
+        int i = 0;
+        for (MethodInfo m : mAllSelfMethods) {
+            methods[i] = m;
+            i++;
+        }
+        methods[i] = method;
+        mAllSelfMethods = methods;
+    }
+
+    public AttributeInfo[] selfAttributes()
+    {
+        if (mSelfAttributes == null) {
+            TreeMap<FieldInfo,AttributeInfo> attrs = new TreeMap<FieldInfo,AttributeInfo>();
+
+            // the ones in the class comment won't have any methods
+            for (AttrTagInfo tag: comment().attrTags()) {
+                FieldInfo field = tag.reference();
+                if (field != null) {
+                    AttributeInfo attr = attrs.get(field);
+                    if (attr == null) {
+                        attr = new AttributeInfo(this, field);
+                        attrs.put(field, attr);
+                    }
+                    tag.setAttribute(attr);
+                }
+            }
+
+            // in the methods
+            for (MethodInfo m: selfMethods()) {
+                for (AttrTagInfo tag: m.comment().attrTags()) {
+                    FieldInfo field = tag.reference();
+                    if (field != null) {
+                        AttributeInfo attr = attrs.get(field);
+                        if (attr == null) {
+                            attr = new AttributeInfo(this, field);
+                            attrs.put(field, attr);
+                        }
+                        tag.setAttribute(attr);
+                        attr.methods.add(m);
+                    }
+                }
+            }
+            
+            //constructors too
+           for (MethodInfo m: constructors()) {
+              for (AttrTagInfo tag: m.comment().attrTags()) {
+                  FieldInfo field = tag.reference();
+                  if (field != null) {
+                      AttributeInfo attr = attrs.get(field);
+                      if (attr == null) {
+                          attr = new AttributeInfo(this, field);
+                          attrs.put(field, attr);
+                      }
+                      tag.setAttribute(attr);
+                      attr.methods.add(m);
+                  }
+              }
+          }
+
+            mSelfAttributes = attrs.values().toArray(new AttributeInfo[attrs.size()]);
+            Arrays.sort(mSelfAttributes, AttributeInfo.comparator);
+        }
+        return mSelfAttributes;
+    }
+
+    public FieldInfo[] enumConstants()
+    {
+        return mEnumConstants;
+    }
+
+    public ClassInfo superclass()
+    {
+        if (!mSuperclassInit) {
+            if (this.checkLevel()) {
+                // rearrange our little inheritance hierarchy, because we need to hide classes that
+                // don't pass checkLevel
+                ClassInfo superclass = mRealSuperclass;
+                while (superclass != null && !superclass.checkLevel()) {
+                    superclass = superclass.mRealSuperclass;
+                }
+                mSuperclass = superclass;
+            } else {
+                mSuperclass = mRealSuperclass;
+            }
+        }
+        return mSuperclass;
+    }
+
+    public ClassInfo realSuperclass()
+    {
+        return mRealSuperclass;
+    }
+
+    /** always the real superclass, not the collapsed one we get through superclass(),
+     * also has the type parameter info if it's generic.
+     */
+    public TypeInfo superclassType()
+    {
+        return mRealSuperclassType;
+    }
+
+    public TypeInfo asTypeInfo()
+    {
+        return mTypeInfo;
+    }
+
+    TypeInfo[] interfaceTypes()
+    {
+        ClassInfo[] infos = interfaces();
+        int len = infos.length;
+        TypeInfo[] types = new TypeInfo[len];
+        for (int i=0; i<len; i++) {
+            types[i] = infos[i].asTypeInfo();
+        }
+        return types;
+    }
+
+    public String htmlPage()
+    {
+        String s = containingPackage().name();
+        s = s.replace('.', '/');
+        s += '/';
+        s += name();
+        s += ".html";
+        s = DroidDoc.javadocDir + s;
+        return s;
+    }
+
+    /** Even indirectly */
+    public boolean isDerivedFrom(ClassInfo cl)
+    {
+        ClassInfo dad = this.superclass();
+        if (dad != null) {
+            if (dad.equals(cl)) {
+                return true;
+            } else {
+                if (dad.isDerivedFrom(cl)) {
+                    return true;
+                }
+            }
+        }
+        for (ClassInfo iface: interfaces()) {
+            if (iface.equals(cl)) {
+                return true;
+            } else {
+                if (iface.isDerivedFrom(cl)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void makeKeywordEntries(List<KeywordEntry> keywords)
+    {
+        if (!checkLevel()) {
+            return;
+        }
+
+        String htmlPage = htmlPage();
+        String qualifiedName = qualifiedName();
+
+        keywords.add(new KeywordEntry(name(), htmlPage,
+                "class in " + containingPackage().name()));
+
+        FieldInfo[] fields = selfFields();
+        FieldInfo[] enumConstants = enumConstants();
+        MethodInfo[] ctors = constructors();
+        MethodInfo[] methods = selfMethods();
+
+        // enum constants
+        for (FieldInfo field: enumConstants()) {
+            if (field.checkLevel()) {
+                keywords.add(new KeywordEntry(field.name(),
+                            htmlPage + "#" + field.anchor(),
+                            "enum constant in " + qualifiedName));
+            }
+        }
+
+        // constants
+        for (FieldInfo field: fields) {
+            if (field.isConstant() && field.checkLevel()) {
+                keywords.add(new KeywordEntry(field.name(),
+                            htmlPage + "#" + field.anchor(),
+                            "constant in " + qualifiedName));
+            }
+        }
+
+        // fields
+        for (FieldInfo field: fields) {
+            if (!field.isConstant() && field.checkLevel()) {
+                keywords.add(new KeywordEntry(field.name(),
+                            htmlPage + "#" + field.anchor(),
+                            "field in " + qualifiedName));
+            }
+        }
+
+        // public constructors
+        for (MethodInfo m: ctors) {
+            if (m.isPublic() && m.checkLevel()) {
+                keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                            htmlPage + "#" + m.anchor(),
+                            "constructor in " + qualifiedName));
+            }
+        }
+
+        // protected constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) {
+            for (MethodInfo m: ctors) {
+                if (m.isProtected() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "constructor in " + qualifiedName));
+                }
+            }
+        }
+
+        // package private constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) {
+            for (MethodInfo m: ctors) {
+                if (m.isPackagePrivate() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "constructor in " + qualifiedName));
+                }
+            }
+        }
+
+        // private constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) {
+            for (MethodInfo m: ctors) {
+                if (m.isPrivate() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "constructor in " + qualifiedName));
+                }
+            }
+        }
+
+        // public methods
+        for (MethodInfo m: methods) {
+            if (m.isPublic() && m.checkLevel()) {
+                keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                            htmlPage + "#" + m.anchor(),
+                            "method in " + qualifiedName));
+            }
+        }
+
+        // protected methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) {
+            for (MethodInfo m: methods) {
+                if (m.isProtected() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "method in " + qualifiedName));
+                }
+            }
+        }
+
+        // package private methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) {
+            for (MethodInfo m: methods) {
+                if (m.isPackagePrivate() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "method in " + qualifiedName));
+                }
+            }
+        }
+
+        // private methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) {
+            for (MethodInfo m: methods) {
+                if (m.isPrivate() && m.checkLevel()) {
+                    keywords.add(new KeywordEntry(m.name() + m.prettySignature(),
+                                htmlPage + "#" + m.anchor(),
+                                "method in " + qualifiedName));
+                }
+            }
+        }
+    }
+
+    public void makeLink(HDF data, String base)
+    {
+        data.setValue(base + ".label", this.name());
+        if (!this.isPrimitive() && this.isIncluded() && this.checkLevel()) {
+            data.setValue(base + ".link", this.htmlPage());
+        }
+    }
+
+    public static void makeLinkListHDF(HDF data, String base, ClassInfo[] classes) {
+        final int N = classes.length;
+        for (int i=0; i<N; i++) {
+            ClassInfo cl = classes[i];
+            if (cl.checkLevel()) {
+                cl.asTypeInfo().makeHDF(data, base + "." + i);
+            }
+        }
+    }
+
+    /**
+     * Used in lists of this class (packages, nested classes, known subclasses)
+     */
+    public void makeShortDescrHDF(HDF data, String base)
+    {
+        mTypeInfo.makeHDF(data, base + ".type");
+        data.setValue(base + ".kind", this.kind());
+        TagInfo.makeHDF(data, base + ".shortDescr", this.firstSentenceTags());
+        TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
+    }
+
+    /**
+     * Turns into the main class page
+     */
+    public void makeHDF(HDF data)
+    {
+        int i, j, n;
+        String name = name();
+        String qualified = qualifiedName();
+        AttributeInfo[] selfAttributes = selfAttributes();
+        MethodInfo[] methods = selfMethods();
+        FieldInfo[] fields = selfFields();
+        FieldInfo[] enumConstants = enumConstants();
+        MethodInfo[] ctors = constructors();
+        ClassInfo[] inners = innerClasses();
+
+        // class name
+        mTypeInfo.makeHDF(data, "class.type");
+        mTypeInfo.makeQualifiedHDF(data, "class.qualifiedType");
+        data.setValue("class.name", name);
+        data.setValue("class.qualified", qualified);
+        String scope = "";
+        if (isProtected()) {
+            data.setValue("class.scope", "protected");
+        }
+        else if (isPublic()) {
+            data.setValue("class.scope", "public");
+        }
+        if (isStatic()) {
+            data.setValue("class.static", "static");
+        }
+        if (isFinal()) {
+            data.setValue("class.final", "final");
+        }
+        if (isAbstract() && !isInterface()) {
+            data.setValue("class.abstract", "abstract");
+        }
+
+        // class info
+        String kind = kind();
+        if (kind != null) {
+            data.setValue("class.kind", kind);
+        }
+
+        // the containing package -- note that this can be passed to type_link,
+        // but it also contains the list of all of the packages
+        containingPackage().makeClassLinkListHDF(data, "class.package");
+
+        // inheritance hierarchy
+        Vector<ClassInfo> superClasses = new Vector<ClassInfo>();
+        superClasses.add(this);
+        ClassInfo supr = superclass();
+        while (supr != null) {
+            superClasses.add(supr);
+            supr = supr.superclass();
+        }
+        n = superClasses.size();
+        for (i=0; i<n; i++) {
+            supr = superClasses.elementAt(n-i-1);
+
+            supr.asTypeInfo().makeQualifiedHDF(data, "class.inheritance." + i + ".class");
+            supr.asTypeInfo().makeHDF(data, "class.inheritance." + i + ".short_class");
+            j = 0;
+            for (TypeInfo t: supr.interfaceTypes()) {
+                t.makeHDF(data, "class.inheritance." + i + ".interfaces." + j);
+                j++;
+            }
+        }
+
+        // class description
+        TagInfo.makeHDF(data, "class.descr", inlineTags());
+        TagInfo.makeHDF(data, "class.seeAlso", comment().seeTags());
+        TagInfo.makeHDF(data, "class.deprecated", deprecatedTags());
+
+        // known subclasses
+        TreeMap<String, ClassInfo> direct = new TreeMap<String, ClassInfo>();
+        TreeMap<String, ClassInfo> indirect = new TreeMap<String, ClassInfo>();
+        ClassInfo[] all = Converter.rootClasses();
+        for (ClassInfo cl: all) {
+            if (cl.superclass() != null && cl.superclass().equals(this)) {
+                direct.put(cl.name(), cl);
+            }
+            else if (cl.isDerivedFrom(this)) {
+                indirect.put(cl.name(), cl);
+            }
+        }
+        // direct
+        i = 0;
+        for (ClassInfo cl: direct.values()) {
+            if (cl.checkLevel()) {
+                cl.makeShortDescrHDF(data, "class.subclasses.direct." + i);
+            }
+            i++;
+        }
+        // indirect
+        i = 0;
+        for (ClassInfo cl: indirect.values()) {
+            if (cl.checkLevel()) {
+                cl.makeShortDescrHDF(data, "class.subclasses.indirect." + i);
+            }
+            i++;
+        }
+
+        // nested classes
+        i=0;
+        for (ClassInfo inner: inners) {
+            if (inner.checkLevel()) {
+                inner.makeShortDescrHDF(data, "class.inners." + i);
+            }
+            i++;
+        }
+
+        // enum constants
+        i=0;
+        for (FieldInfo field: enumConstants) {
+            if (field.isConstant()) {
+                field.makeHDF(data, "class.enumConstants." + i);
+                i++;
+            }
+        }
+
+        // constants
+        i=0;
+        for (FieldInfo field: fields) {
+            if (field.isConstant()) {
+                field.makeHDF(data, "class.constants." + i);
+                i++;
+            }
+        }
+
+        // fields
+        i=0;
+        for (FieldInfo field: fields) {
+            if (!field.isConstant()) {
+                field.makeHDF(data, "class.fields." + i);
+                i++;
+            }
+        }
+
+        // public constructors
+        i=0;
+        for (MethodInfo ctor: ctors) {
+            if (ctor.isPublic()) {
+                ctor.makeHDF(data, "class.ctors.public." + i);
+                i++;
+            }
+        }
+
+        // protected constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) {
+            i=0;
+            for (MethodInfo ctor: ctors) {
+                if (ctor.isProtected()) {
+                    ctor.makeHDF(data, "class.ctors.protected." + i);
+                    i++;
+                }
+            }
+        }
+
+        // package private constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) {
+            i=0;
+            for (MethodInfo ctor: ctors) {
+                if (ctor.isPackagePrivate()) {
+                    ctor.makeHDF(data, "class.ctors.package." + i);
+                    i++;
+                }
+            }
+        }
+
+        // private constructors
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) {
+            i=0;
+            for (MethodInfo ctor: ctors) {
+                if (ctor.isPrivate()) {
+                    ctor.makeHDF(data, "class.ctors.private." + i);
+                    i++;
+                }
+            }
+        }
+
+        // public methods
+        i=0;
+        for (MethodInfo method: methods) {
+            if (method.isPublic()) {
+                method.makeHDF(data, "class.methods.public." + i);
+                i++;
+            }
+        }
+
+        // protected methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PROTECTED)) {
+            i=0;
+            for (MethodInfo method: methods) {
+                if (method.isProtected()) {
+                    method.makeHDF(data, "class.methods.protected." + i);
+                    i++;
+                }
+            }
+        }
+
+        // package private methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PACKAGE)) {
+            i=0;
+            for (MethodInfo method: methods) {
+                if (method.isPackagePrivate()) {
+                    method.makeHDF(data, "class.methods.package." + i);
+                    i++;
+                }
+            }
+        }
+
+        // private methods
+        if (DroidDoc.checkLevel(DroidDoc.SHOW_PRIVATE)) {
+            i=0;
+            for (MethodInfo method: methods) {
+                if (method.isPrivate()) {
+                    method.makeHDF(data, "class.methods.private." + i);
+                    i++;
+                }
+            }
+        }
+
+        // xml attributes
+        i=0;
+        for (AttributeInfo attr: selfAttributes) {
+            if (attr.checkLevel()) {
+                attr.makeHDF(data, "class.attrs." + i);
+                i++;
+            }
+        }
+
+        // inherited methods
+        Set<ClassInfo> interfaces = new TreeSet<ClassInfo>();
+        addInterfaces(interfaces(), interfaces);
+        ClassInfo cl = superclass();
+        i=0;
+        while (cl != null) {
+            addInterfaces(cl.interfaces(), interfaces);
+            makeInheritedHDF(data, i, cl);
+            cl = cl.superclass();
+            i++;
+        }
+        for (ClassInfo iface: interfaces) {
+            makeInheritedHDF(data, i, iface);
+            i++;
+        }
+    }
+
+    private static void addInterfaces(ClassInfo[] ifaces, Set<ClassInfo> out)
+    {
+        for (ClassInfo cl: ifaces) {
+            out.add(cl);
+            addInterfaces(cl.interfaces(), out);
+        }
+    }
+
+    private static void makeInheritedHDF(HDF data, int index, ClassInfo cl)
+    {
+        int i;
+
+        String base = "class.inherited." + index;
+        data.setValue(base + ".qualified", cl.qualifiedName());
+        if (cl.checkLevel()) {
+            data.setValue(base + ".link", cl.htmlPage());
+        }
+        String kind = cl.kind();
+        if (kind != null) {
+            data.setValue(base + ".kind", kind);
+        }
+        
+        // xml attributes
+        i=0;
+        for (AttributeInfo attr: cl.selfAttributes()) {
+            attr.makeHDF(data, base + ".attrs." + i);
+            i++;
+        }
+
+        // methods
+        i=0;
+        for (MethodInfo method: cl.selfMethods()) {
+            method.makeHDF(data, base + ".methods." + i);
+            i++;
+        }
+
+        // fields
+        i=0;
+        for (FieldInfo field: cl.selfFields()) {
+            if (!field.isConstant()) {
+                field.makeHDF(data, base + ".fields." + i);
+                i++;
+            }
+        }
+
+        // constants
+        i=0;
+        for (FieldInfo field: cl.selfFields()) {
+            if (field.isConstant()) {
+                field.makeHDF(data, base + ".constants." + i);
+                i++;
+            }
+        }
+    }
+
+    public boolean isHidden()
+    {
+        int val = mHidden;
+        if (val >= 0) {
+            return val != 0;
+        } else {
+            boolean v = isHiddenImpl();
+            mHidden = v ? 1 : 0;
+            return v;
+        }
+    }
+
+    public boolean isHiddenImpl()
+    {
+        ClassInfo cl = this;
+        while (cl != null) {
+            PackageInfo pkg = cl.containingPackage();
+            if (pkg.isHidden()) {
+                return true;
+            }
+            if (cl.comment().isHidden()) {
+                return true;
+            }
+            cl = cl.containingClass();
+        }
+        return false;
+    }
+
+    private MethodInfo matchMethod(MethodInfo[] methods, String name,
+                                    String[] params, String[] dimensions)
+    {
+        int len = methods.length;
+        for (int i=0; i<len; i++) {
+            MethodInfo method = methods[i];
+            if (method.name().equals(name)) {
+                if (params == null) {
+                    return method;
+                } else {
+                    if (method.matchesParams(params, dimensions)) {
+                        return method;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public MethodInfo findMethod(String name,
+                                    String[] params, String[] dimensions)
+    {
+        // first look on our class, and our superclasses
+
+        // for methods
+        MethodInfo rv;
+        rv = matchMethod(methods(), name, params, dimensions);
+
+        if (rv != null) {
+            return rv;
+        }
+
+        // for constructors
+        rv = matchMethod(constructors(), name, params, dimensions);
+        if (rv != null) {
+            return rv;
+        }
+
+        // then recursively look at our containing class
+        ClassInfo containing = containingClass();
+        if (containing != null) {
+            return containing.findMethod(name, params, dimensions);
+        }
+
+        return null;
+    }
+
+    private ClassInfo searchInnerClasses(String[] nameParts, int index)
+    {
+        String part = nameParts[index];
+
+        ClassInfo[] inners = mInnerClasses;
+        for (ClassInfo in: inners) {
+            String[] innerParts = in.nameParts();
+            if (part.equals(innerParts[innerParts.length-1])) {
+                if (index == nameParts.length-1) {
+                    return in;
+                } else {
+                    return in.searchInnerClasses(nameParts, index+1);
+                }
+            }
+        }
+        return null;
+    }
+
+    public ClassInfo extendedFindClass(String className)
+    {
+        // ClassDoc.findClass has this bug that we're working around here:
+        // If you have a class PackageManager with an inner class PackageInfo
+        // and you call it with "PackageInfo" it doesn't find it.
+        return searchInnerClasses(className.split("\\."), 0);
+    }
+
+    public ClassInfo findClass(String className)
+    {
+        return Converter.obtainClass(mClass.findClass(className));
+    }
+
+    public ClassInfo findInnerClass(String className)
+    {
+        // ClassDoc.findClass won't find inner classes.  To deal with that,
+        // we try what they gave us first, but if that didn't work, then
+        // we see if there are any periods in className, and start searching
+        // from there.
+        String[] nodes = className.split("\\.");
+        ClassDoc cl = mClass;
+        for (String n: nodes) {
+            cl = cl.findClass(n);
+            if (cl == null) {
+                return null;
+            }
+        }
+        return Converter.obtainClass(cl);
+    }
+
+    public FieldInfo findField(String name)
+    {
+        // first look on our class, and our superclasses
+        for (FieldInfo f: fields()) {
+            if (f.name().equals(name)) {
+                return f;
+            }
+        }
+        
+        // then look at our enum constants (these are really fields, maybe
+        // they should be mixed into fields().  not sure)
+        for (FieldInfo f: enumConstants()) {
+            if (f.name().equals(name)) {
+                return f;
+            }
+        }
+
+        // then recursively look at our containing class
+        ClassInfo containing = containingClass();
+        if (containing != null) {
+            return containing.findField(name);
+        }
+
+        return null;
+    }
+
+    public static ClassInfo[] sortByName(ClassInfo[] classes)
+    {
+        int i;
+        Sorter[] sorted = new Sorter[classes.length];
+        for (i=0; i<sorted.length; i++) {
+            ClassInfo cl = classes[i];
+            sorted[i] = new Sorter(cl.name(), cl);
+        }
+
+        Arrays.sort(sorted);
+
+        ClassInfo[] rv = new ClassInfo[classes.length];
+        for (i=0; i<rv.length; i++) {
+            rv[i] = (ClassInfo)sorted[i].data;
+        }
+
+        return rv;
+    }
+
+    public boolean equals(ClassInfo that)
+    {
+        if (that != null) {
+            return this.qualifiedName().equals(that.qualifiedName());
+        } else {
+            return false;
+        }
+    }
+    
+    public void setNonWrittenConstructors(MethodInfo[] nonWritten) {
+        mNonWrittenConstructors = nonWritten;
+    }
+    
+    public MethodInfo[] getNonWrittenConstructors() {
+        return mNonWrittenConstructors;
+    }
+
+    public String kind()
+    {
+        if (isOrdinaryClass()) {
+            return "class";
+        }
+        else if (isInterface()) {
+            return "interface";
+        }
+        else if (isEnum()) {
+            return "enum";
+        }
+        else if (isError()) {
+            return "class";
+        }
+        else if (isException()) {
+            return "class";
+        }
+        else if (isAnnotation()) {
+            return "@interface";
+        }
+        return null;
+    }
+    
+    public void setHiddenMethods(MethodInfo[] mInfo){
+        mHiddenMethods = mInfo;
+    }
+    public MethodInfo[] getHiddenMethods(){
+        return mHiddenMethods;
+    }
+    public String toString(){
+        return this.qualifiedName();
+    }
+    
+    public void setReasonIncluded(String reason) {
+        mReasonIncluded = reason;
+    }
+    
+    public String getReasonIncluded() {
+        return mReasonIncluded; 
+    }
+
+    private ClassDoc mClass;
+
+    // ctor
+    private boolean mIsPublic;
+    private boolean mIsProtected;
+    private boolean mIsPackagePrivate;
+    private boolean mIsPrivate;
+    private boolean mIsStatic;
+    private boolean mIsInterface;
+    private boolean mIsAbstract;
+    private boolean mIsOrdinaryClass;
+    private boolean mIsException;
+    private boolean mIsError;
+    private boolean mIsEnum;
+    private boolean mIsAnnotation;
+    private boolean mIsFinal;
+    private boolean mIsIncluded;
+    private String mName;
+    private String mQualifiedName;
+    private String mQualifiedTypeName;
+    private boolean mIsPrimitive;
+    private TypeInfo mTypeInfo;
+    private String[] mNameParts;
+
+    // init
+    private ClassInfo[] mRealInterfaces;
+    private ClassInfo[] mInterfaces;
+    private TypeInfo[] mRealInterfaceTypes;
+    private ClassInfo[] mInnerClasses;
+    private MethodInfo[] mAllConstructors;
+    private MethodInfo[] mAllSelfMethods;
+    private MethodInfo[] mAnnotationElements; // if this class is an annotation
+    private FieldInfo[] mAllSelfFields;
+    private FieldInfo[] mEnumConstants;
+    private PackageInfo mContainingPackage;
+    private ClassInfo mContainingClass;
+    private ClassInfo mRealSuperclass;
+    private TypeInfo mRealSuperclassType;
+    private ClassInfo mSuperclass;
+    private AnnotationInstanceInfo[] mAnnotations;
+    private boolean mSuperclassInit;
+    private boolean mDeprecatedKnown;
+
+    // lazy
+    private MethodInfo[] mConstructors;
+    private ClassInfo[] mRealInnerClasses;
+    private MethodInfo[] mSelfMethods;
+    private FieldInfo[] mSelfFields;
+    private AttributeInfo[] mSelfAttributes;
+    private MethodInfo[] mMethods;
+    private FieldInfo[] mFields;
+    private TypeInfo[] mTypeParameters;
+    private MethodInfo[] mHiddenMethods;
+    private int mHidden = -1;
+    private int mCheckLevel = -1;
+    private String mReasonIncluded;
+    private MethodInfo[] mNonWrittenConstructors;
+    private boolean mIsDeprecated;
+}
diff --git a/tools/droiddoc/src/ClearPage.java b/tools/droiddoc/src/ClearPage.java
new file mode 100644
index 0000000..2a8fced
--- /dev/null
+++ b/tools/droiddoc/src/ClearPage.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.sun.javadoc.*;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class ClearPage
+{
+    /*
+    public ClearPage()
+    {
+        String templ = "templates/index.cs";
+        String filename = "docs/index.html";
+
+        data.setValue("A.B.C", "1");
+        data.setValue("A.B.D", "2");
+    }
+    */
+
+    public static ArrayList<String> hdfFiles = new ArrayList<String>();
+
+    private static ArrayList<String> mTemplateDirs = new ArrayList<String>();
+    private static boolean mTemplateDirSet = false;
+
+    public static String outputDir = "docs";
+    public static String htmlDir = null;
+    public static String toroot = null;
+
+    public static void addTemplateDir(String dir)
+    {
+        mTemplateDirSet = true;
+        mTemplateDirs.add(dir);
+
+        File hdfFile = new File(dir, "data.hdf");
+        if (hdfFile.canRead()) {
+            hdfFiles.add(hdfFile.getPath());
+        }
+    }
+
+    private static int countSlashes(String s)
+    {
+        final int N = s.length();
+        int slashcount = 0;
+        for (int i=0; i<N; i++) {
+            if (s.charAt(i) == '/') {
+                slashcount++;
+            }
+        }
+        return slashcount;
+    }
+
+    public static void write(HDF data, String templ, String filename)
+    {
+        write(data, templ, filename, false);
+    }
+
+    public static void write(HDF data, String templ, String filename, boolean fullPath)
+    {
+        if (htmlDir != null) {
+            data.setValue("hasindex", "true");
+        }
+
+        String toroot;
+        if (ClearPage.toroot != null) {
+            toroot = ClearPage.toroot;
+        } else {
+            int slashcount = countSlashes(filename);
+            if (slashcount > 0) {
+                toroot = "";
+                for (int i=0; i<slashcount; i++) {
+                    toroot += "../";
+                }
+            } else {
+                toroot = "./";
+            }
+        }
+        data.setValue("toroot", toroot);
+
+        data.setValue("filename", filename);
+
+        if (!fullPath) {
+            filename = outputDir + "/" + filename;
+        }
+
+        int i=0;
+        if (htmlDir != null) {
+            data.setValue("hdf.loadpaths." + i, htmlDir);
+            i++;
+        }
+        if (mTemplateDirSet) {
+            for (String dir: mTemplateDirs) {
+                data.setValue("hdf.loadpaths." + i, dir);
+                i++;
+            }
+        } else {
+            data.setValue("hdf.loadpaths." + i, "templates");
+        }
+        
+        CS cs = new CS(data);
+        cs.parseFile(templ);
+
+        File file = new File(outputFilename(filename));
+        
+        ensureDirectory(file);
+
+        OutputStreamWriter stream = null;
+        try {
+            stream = new OutputStreamWriter(
+                            new FileOutputStream(file));
+            String rendered = cs.render();
+            stream.write(rendered, 0, rendered.length());
+        }
+        catch (IOException e) {
+            System.out.println("error: " + e.getMessage() + "; when writing file: " + filename);
+        }
+        finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                }
+                catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    // recursively create the directories to the output
+    public static void ensureDirectory(File f)
+    {
+        File parent = f.getParentFile();
+        if (parent != null) {
+            parent.mkdirs();
+        }
+    }
+
+    public static void copyFile(File from, String toPath)
+    {
+        File to = new File(outputDir + "/" + toPath);
+        FileInputStream in;
+        FileOutputStream out;
+        try {
+            if (!from.exists()) {
+                throw new IOException();
+            }
+            in = new FileInputStream(from);
+        }
+        catch (IOException e) {
+            System.err.println(from.getAbsolutePath() + ": Error opening file");
+            return ;
+        }
+        ensureDirectory(to);
+        try {
+            out = new FileOutputStream(to);
+        }
+        catch (IOException e) {
+            System.err.println(from.getAbsolutePath() + ": Error opening file");
+            return ;
+        }
+
+        long sizel = from.length();
+        final int maxsize = 64*1024;
+        int size = sizel > maxsize ? maxsize : (int)sizel;
+        byte[] buf = new byte[size];
+        while (true) {
+            try {
+                size = in.read(buf);
+            }
+            catch (IOException e) {
+                System.err.println(from.getAbsolutePath()
+                        + ": error reading file");
+                break;
+            }
+            if (size > 0) {
+                try {
+                    out.write(buf, 0, size);
+                }
+                catch (IOException e) {
+                    System.err.println(from.getAbsolutePath()
+                        + ": error writing file");
+                }
+            } else {
+                break;
+            }
+        }
+        try {
+            in.close();
+        }
+        catch (IOException e) {
+        }
+        try {
+            out.close();
+        }
+        catch (IOException e) {
+        }
+    }
+    
+    /** Takes a string that ends w/ .html and changes the .html to htmlExtension */
+    public static String outputFilename(String htmlFile) {
+        if (!DroidDoc.htmlExtension.equals(".html") && htmlFile.endsWith(".html")) {
+            return htmlFile.substring(0, htmlFile.length()-5) + DroidDoc.htmlExtension;
+        } else {
+            return htmlFile;
+        }
+    }
+
+}
diff --git a/tools/droiddoc/src/Comment.java b/tools/droiddoc/src/Comment.java
new file mode 100644
index 0000000..3a24357
--- /dev/null
+++ b/tools/droiddoc/src/Comment.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+
+public class Comment
+{
+    static final Pattern LEADING_WHITESPACE = Pattern.compile(
+                                "^[ \t\n\r]*(.*)$",
+                                Pattern.DOTALL);
+
+    static final Pattern TAG_BEGIN = Pattern.compile(
+                                "[\r\n][\r\n \t]*@",
+                                Pattern.DOTALL);
+
+    static final Pattern TAG = Pattern.compile(
+                                "(@[^ \t\r\n]+)[ \t\r\n]+(.*)",
+                                Pattern.DOTALL);
+
+    static final Pattern INLINE_TAG = Pattern.compile(
+                                "(.*?)\\{(@[^ \t\r\n\\}]+)[ \t\r\n]*(.*?)\\}",
+                                Pattern.DOTALL);
+
+    static final Pattern FIRST_SENTENCE = Pattern.compile(
+                                "((.*?)\\.)[ \t\r\n\\<](.*)",
+                                Pattern.DOTALL);
+
+    private static final String[] KNOWN_TAGS = new String[] {
+            "@author",
+            "@since",
+            "@version",
+            "@deprecated",
+            "@undeprecate",
+            "@docRoot",
+            "@inheritDoc",
+            "@more",
+            "@code",
+            "@samplecode",
+            "@sample",
+            "@include",
+            "@serial",
+            "@com.intel.drl.spec_ref",
+            "@ar.org.fitc.spec_ref",
+        };
+
+    public Comment(String text, ContainerInfo base, SourcePositionInfo sp)
+    {
+        mText = text;
+        mBase = base;
+        // sp now points to the end of the text, not the beginning!
+        mPosition = SourcePositionInfo.findBeginning(sp, text);
+    }
+
+    private void parseRegex(String text)
+    {
+        Matcher m;
+
+        m = LEADING_WHITESPACE.matcher(text);
+        m.matches();
+        text = m.group(1);
+
+        m = TAG_BEGIN.matcher(text);
+
+        int start = 0;
+        int end = 0;
+        while (m.find()) {
+            end = m.start();
+
+            tag(text, start, end);
+
+            start = m.end()-1; // -1 is the @
+        }
+        end = text.length();
+        tag(text, start, end);
+    }
+
+    private void tag(String text, int start, int end)
+    {
+        SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, start);
+
+        if (start >= 0 && end > 0 && (end-start) > 0) {
+            text = text.substring(start, end);
+
+            Matcher m = TAG.matcher(text);
+            if (m.matches()) {
+                // out of line tag
+                tag(m.group(1), m.group(2), false, pos);
+            } else {
+                // look for inline tags
+                m = INLINE_TAG.matcher(text);
+                start = 0;
+                while (m.find()) {
+                    String str = m.group(1);
+                    String tagname = m.group(2);
+                    String tagvalue = m.group(3);
+                    tag(null, m.group(1), true, pos);
+                    tag(tagname, tagvalue, true, pos);
+                    start = m.end();
+                }
+                int len = text.length();
+                if (start != len) {
+                    tag(null, text.substring(start), true, pos);
+                }
+            }
+        }
+    }
+
+    private void tag(String name, String text, boolean isInline, SourcePositionInfo pos)
+    {
+        /*
+        String s = isInline ? "inline" : "outofline";
+        System.out.println("---> " + s
+                + " name=[" + name + "] text=[" + text + "]");
+        */
+        if (name == null) {
+            mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos));
+        }
+        else if (name.equals("@param")) {
+            mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos));
+        }
+        else if (name.equals("@see")) {
+            mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos));
+        }
+        else if (name.equals("@link") || name.equals("@linkplain")) {
+            mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
+        }
+        else if (name.equals("@throws") || name.equals("@exception")) {
+            mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos));
+        }
+        else if (name.equals("@return")) {
+            mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos));
+        }
+        else if (name.equals("@deprecated")) {
+            if (text.length() == 0) {
+                Errors.error(Errors.MISSING_COMMENT, pos,
+                        "@deprecated tag with no explanatory comment");
+                text = "No replacement.";
+            }
+            mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos));
+        }
+        else if (name.equals("@literal")) {
+            mInlineTagsList.add(new LiteralTagInfo(name, name, text, pos));
+        }
+        else if (name.equals("@hide") || name.equals("@doconly")) {
+            // nothing
+        }
+        else if (name.equals("@attr")) {
+            AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
+            mAttrTagsList.add(tag);
+            Comment c = tag.description();
+            if (c != null) {
+                for (TagInfo t: c.tags()) {
+                    mInlineTagsList.add(t);
+                }
+            }
+        }
+        else if (name.equals("@undeprecate")) {
+            mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos));
+        }
+        else if (name.equals("@include") || name.equals("@sample")) {
+            mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos));
+        }
+        else {
+            boolean known = false;
+            for (String s: KNOWN_TAGS) {
+                if (s.equals(name)) {
+                    known = true;
+                    break;
+                }
+            }
+            if (!known) {
+                Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos),
+                        "Unknown tag: " + name);
+            }
+            TagInfo t = new TextTagInfo(name, name, text, pos);
+            if (isInline) {
+                mInlineTagsList.add(t);
+            } else {
+                mTagsList.add(t);
+            }
+        }
+    }
+
+    private void parseBriefTags()
+    {
+        int N = mInlineTagsList.size();
+
+        // look for "@more" tag, which means that we might go past the first sentence.
+        int more = -1;
+        for (int i=0; i<N; i++) {
+            if (mInlineTagsList.get(i).name().equals("@more")) {
+                more = i;
+            } 
+        }
+          if (more >= 0) {
+            for (int i=0; i<more; i++) {
+                mBriefTagsList.add(mInlineTagsList.get(i));
+            }
+        } else {
+            for (int i=0; i<N; i++) {
+                TagInfo t = mInlineTagsList.get(i);
+                if (t.name().equals("Text")) {
+                    Matcher m = FIRST_SENTENCE.matcher(t.text());
+                    if (m.matches()) {
+                        String text = m.group(1);
+                        TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position());
+                        mBriefTagsList.add(firstSentenceTag);
+                        break;
+                    }
+                }
+                mBriefTagsList.add(t);
+                
+            }
+        }
+    }
+
+    public TagInfo[] tags()
+    {
+        init();
+        return mInlineTags;
+    }
+
+    public TagInfo[] tags(String name)
+    {
+        init();
+        ArrayList<TagInfo> results = new ArrayList<TagInfo>();
+        int N = mInlineTagsList.size();
+        for (int i=0; i<N; i++) {
+            TagInfo t = mInlineTagsList.get(i);
+            if (t.name().equals(name)) {
+                results.add(t);
+            }
+        }
+        return results.toArray(new TagInfo[results.size()]);
+    }
+
+    public ParamTagInfo[] paramTags()
+    {
+        init();
+        return mParamTags;
+    }
+
+    public SeeTagInfo[] seeTags()
+    {
+        init();
+        return mSeeTags;
+    }
+
+    public ThrowsTagInfo[] throwsTags()
+    {
+        init();
+        return mThrowsTags;
+    }
+
+    public TagInfo[] returnTags()
+    {
+        init();
+        return mReturnTags;
+    }
+
+    public TagInfo[] deprecatedTags()
+    {
+        init();
+        return mDeprecatedTags;
+    }
+
+    public TagInfo[] undeprecateTags()
+    {
+        init();
+        return mUndeprecateTags;
+    }
+
+    public AttrTagInfo[] attrTags()
+    {
+        init();
+        return mAttrTags;
+    }
+
+    public TagInfo[] briefTags()
+    {
+        init();
+        return mBriefTags;
+    }
+
+    public boolean isHidden()
+    {
+        if (mHidden >= 0) {
+            return mHidden != 0;
+        } else {
+            if (DroidDoc.checkLevel(DroidDoc.SHOW_HIDDEN)) {
+                mHidden = 0;
+                return false;
+            }
+            boolean b = mText.indexOf("@hide") >= 0;
+            mHidden = b ? 1 : 0;
+            return b;
+        }
+    }
+    
+    public boolean isDocOnly() {
+        if (mDocOnly >= 0) {
+            return mDocOnly != 0;
+        } else {
+            boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0);
+            mDocOnly = b ? 1 : 0;
+            return b;
+        }
+    }
+
+    private void init()
+    {
+        if (!mInitialized) {
+            initImpl();
+        }
+    }
+
+    private void initImpl()
+    {
+        isHidden();
+        isDocOnly();
+        parseRegex(mText);
+        parseBriefTags();
+        mText = null;
+        mInitialized = true;
+
+        mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]);
+        mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]);
+        mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]);
+        mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]);
+        mReturnTags = ParsedTagInfo.joinTags(mReturnTagsList.toArray(
+                                             new ParsedTagInfo[mReturnTagsList.size()]));
+        mDeprecatedTags = ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray(
+                                        new ParsedTagInfo[mDeprecatedTagsList.size()]));
+        mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]);
+        mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]);
+        mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]);
+
+        mParamTagsList = null;
+        mSeeTagsList = null;
+        mThrowsTagsList = null;
+        mReturnTagsList = null;
+        mDeprecatedTagsList = null;
+        mUndeprecateTagsList = null;
+        mAttrTagsList = null;
+        mBriefTagsList = null;
+    }
+
+    boolean mInitialized;
+    int mHidden = -1;
+    int mDocOnly = -1;
+    String mText;
+    ContainerInfo mBase;
+    SourcePositionInfo mPosition;
+    int mLine = 1;
+
+    TagInfo[] mInlineTags;
+    TagInfo[] mTags;
+    ParamTagInfo[] mParamTags;
+    SeeTagInfo[] mSeeTags;
+    ThrowsTagInfo[] mThrowsTags;
+    TagInfo[] mBriefTags;
+    TagInfo[] mReturnTags;
+    TagInfo[] mDeprecatedTags;
+    TagInfo[] mUndeprecateTags;
+    AttrTagInfo[] mAttrTags;
+
+    ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>();
+    ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>();
+    ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>();
+    ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>();
+    ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>();
+    ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>();
+    ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>();
+    ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>();
+    ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
+    ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
+
+    
+}
diff --git a/tools/droiddoc/src/ContainerInfo.java b/tools/droiddoc/src/ContainerInfo.java
new file mode 100644
index 0000000..b8a3e10
--- /dev/null
+++ b/tools/droiddoc/src/ContainerInfo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public interface ContainerInfo
+{
+    public String qualifiedName();
+    public boolean checkLevel();
+}
diff --git a/tools/droiddoc/src/Converter.java b/tools/droiddoc/src/Converter.java
new file mode 100644
index 0000000..4014f7f
--- /dev/null
+++ b/tools/droiddoc/src/Converter.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.sun.javadoc.*;
+import com.sun.tools.doclets.*;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class Converter
+{
+    private static RootDoc root;
+
+    public static void makeInfo(RootDoc r)
+    {
+        root = r;
+
+        int N, i;
+
+        // create the objects
+        ClassDoc[] classDocs = r.classes();
+        N = classDocs.length;
+        for (i=0; i<N; i++) {
+            Converter.obtainClass(classDocs[i]);
+        }
+        ArrayList<ClassInfo> classesNeedingInit2 = new ArrayList<ClassInfo>();
+        // fill in the fields that reference other classes
+        while (mClassesNeedingInit.size() > 0) {
+            i = mClassesNeedingInit.size()-1;
+            ClassNeedingInit clni = mClassesNeedingInit.get(i);
+            mClassesNeedingInit.remove(i);
+
+            initClass(clni.c, clni.cl);
+            classesNeedingInit2.add(clni.cl);
+        }
+        mClassesNeedingInit = null;
+        for (ClassInfo cl: classesNeedingInit2) {
+            cl.init2();
+        }
+
+        finishAnnotationValueInit();
+
+        // fill in the "root" stuff
+        mRootClasses = Converter.convertClasses(r.classes());
+    }
+
+    private static ClassInfo[] mRootClasses;
+    public static ClassInfo[] rootClasses()
+    {
+        return mRootClasses;
+    }
+
+    public static ClassInfo[] allClasses() {
+        return (ClassInfo[])mClasses.all();
+    }
+
+    private static void initClass(ClassDoc c, ClassInfo cl)
+    {
+        MethodDoc[] annotationElements;
+        if (c instanceof AnnotationTypeDoc) {
+            annotationElements = ((AnnotationTypeDoc)c).elements();
+        } else {
+            annotationElements = new MethodDoc[0];
+        }
+        cl.init(Converter.obtainType(c),
+                Converter.convertClasses(c.interfaces()),
+                Converter.convertTypes(c.interfaceTypes()),
+                Converter.convertClasses(c.innerClasses()),
+                Converter.convertMethods(c.constructors(false)),
+                Converter.convertMethods(c.methods(false)),
+                Converter.convertMethods(annotationElements),
+                Converter.convertFields(c.fields(false)),
+                Converter.convertFields(c.enumConstants()),
+                Converter.obtainPackage(c.containingPackage()),
+                Converter.obtainClass(c.containingClass()),
+                Converter.obtainClass(c.superclass()),
+                Converter.obtainType(c.superclassType()),
+                Converter.convertAnnotationInstances(c.annotations())
+                );
+          cl.setHiddenMethods(Converter.getHiddenMethods(c.methods(false)));
+          cl.setNonWrittenConstructors(Converter.convertNonWrittenConstructors(c.constructors(false)));
+          cl.init3(Converter.convertTypes(c.typeParameters()), Converter.convertClasses(c.innerClasses(false)));
+    }
+
+    public static ClassInfo obtainClass(String className)
+    {
+        return Converter.obtainClass(root.classNamed(className));
+    }
+
+    public static PackageInfo obtainPackage(String packageName)
+    {
+        return Converter.obtainPackage(root.packageNamed(packageName));
+    }
+
+    private static TagInfo convertTag(Tag tag)
+    {
+        return new TextTagInfo(tag.name(), tag.kind(), tag.text(),
+                                Converter.convertSourcePosition(tag.position()));
+    }
+
+    private static ThrowsTagInfo convertThrowsTag(ThrowsTag tag,
+                                                ContainerInfo base)
+    {
+        return new ThrowsTagInfo(tag.name(), tag.text(), tag.kind(),
+                              Converter.obtainClass(tag.exception()),
+                              tag.exceptionComment(), base,
+                              Converter.convertSourcePosition(tag.position()));
+    }
+
+    private static ParamTagInfo convertParamTag(ParamTag tag,
+                                                ContainerInfo base)
+    {
+        return new ParamTagInfo(tag.name(), tag.kind(), tag.text(),
+                              tag.isTypeParameter(), tag.parameterComment(),
+                              tag.parameterName(),
+                              base,
+                              Converter.convertSourcePosition(tag.position()));
+    }
+
+    private static SeeTagInfo convertSeeTag(SeeTag tag, ContainerInfo base)
+    {
+        return new SeeTagInfo(tag.name(), tag.kind(), tag.text(), base,
+                              Converter.convertSourcePosition(tag.position()));
+    }
+
+    private static SourcePositionInfo convertSourcePosition(SourcePosition sp)
+    {
+        if (sp == null) {
+            return null;
+        }
+        return new SourcePositionInfo(sp.file().toString(), sp.line(),
+                                        sp.column());
+    }
+
+    public static TagInfo[] convertTags(Tag[] tags, ContainerInfo base)
+    {
+        int len = tags.length;
+        TagInfo[] out = new TagInfo[len];
+        for (int i=0; i<len; i++) {
+            Tag t = tags[i];
+            /*
+            System.out.println("Tag name='" + t.name() + "' kind='"
+                    + t.kind() + "'");
+            */
+            if (t instanceof SeeTag) {
+                out[i] = Converter.convertSeeTag((SeeTag)t, base);
+            }
+            else if (t instanceof ThrowsTag) {
+                out[i] = Converter.convertThrowsTag((ThrowsTag)t, base);
+            }
+            else if (t instanceof ParamTag) {
+                out[i] = Converter.convertParamTag((ParamTag)t, base);
+            }
+            else {
+                out[i] = Converter.convertTag(t);
+            }
+        }
+        return out;
+    }
+
+    public static ClassInfo[] convertClasses(ClassDoc[] classes)
+    {
+        if (classes == null) return null;
+        int N = classes.length;
+        ClassInfo[] result = new ClassInfo[N];
+        for (int i=0; i<N; i++) {
+            result[i] = Converter.obtainClass(classes[i]);
+        }
+        return result;
+    }
+
+    private static ParameterInfo convertParameter(Parameter p, SourcePosition pos)
+    {
+        if (p == null) return null;
+        ParameterInfo pi = new ParameterInfo(p.name(), p.typeName(),
+                Converter.obtainType(p.type()),
+                Converter.convertSourcePosition(pos));
+        return pi;
+    }
+
+    private static ParameterInfo[] convertParameters(Parameter[] p, MemberDoc m)
+    {
+        SourcePosition pos = m.position();
+        int len = p.length;
+        ParameterInfo[] q = new ParameterInfo[len];
+        for (int i=0; i<len; i++) {
+            q[i] = Converter.convertParameter(p[i], pos);
+        }
+        return q;
+    }
+
+    private static TypeInfo[] convertTypes(Type[] p)
+    {
+        if (p == null) return null;
+        int len = p.length;
+        TypeInfo[] q = new TypeInfo[len];
+        for (int i=0; i<len; i++) {
+            q[i] = Converter.obtainType(p[i]);
+        }
+        return q;
+    }
+
+    private Converter()
+    {
+    }
+
+    private static class ClassNeedingInit
+    {
+        ClassNeedingInit(ClassDoc c, ClassInfo cl)
+        {
+            this.c = c;
+            this.cl = cl;
+        }
+        ClassDoc c;
+        ClassInfo cl;
+    };
+    private static ArrayList<ClassNeedingInit> mClassesNeedingInit
+                                            = new ArrayList<ClassNeedingInit>();
+
+    static ClassInfo obtainClass(ClassDoc o)
+    {
+        return (ClassInfo)mClasses.obtain(o);
+    }
+    private static Cache mClasses = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            ClassDoc c = (ClassDoc)o;
+            ClassInfo cl = new ClassInfo(
+                    c,
+                    c.getRawCommentText(),
+                    Converter.convertSourcePosition(c.position()),
+                    c.isPublic(),
+                    c.isProtected(),
+                    c.isPackagePrivate(),
+                    c.isPrivate(),
+                    c.isStatic(),
+                    c.isInterface(),
+                    c.isAbstract(),
+                    c.isOrdinaryClass(),
+                    c.isException(),
+                    c.isError(),
+                    c.isEnum(),
+                    (c instanceof AnnotationTypeDoc),
+                    c.isFinal(),
+                    c.isIncluded(),
+                    c.name(),
+                    c.qualifiedName(),
+                    c.qualifiedTypeName(),
+                    c.isPrimitive());
+            if (mClassesNeedingInit != null) {
+                mClassesNeedingInit.add(new ClassNeedingInit(c, cl));
+            }
+            return cl;
+        }
+        protected void made(Object o, Object r)
+        {
+            if (mClassesNeedingInit == null) {
+                initClass((ClassDoc)o, (ClassInfo)r);
+                ((ClassInfo)r).init2();
+            }
+        } 
+        ClassInfo[] all()
+        {
+            return (ClassInfo[])mCache.values().toArray(new ClassInfo[mCache.size()]);
+        }
+    };
+    
+    private static MethodInfo[] getHiddenMethods(MethodDoc[] methods){
+      if (methods == null) return null;
+      ArrayList<MethodInfo> out = new ArrayList<MethodInfo>();
+      int N = methods.length;
+      for (int i=0; i<N; i++) {
+          MethodInfo m = Converter.obtainMethod(methods[i]);
+          //System.out.println(m.toString() + ": ");
+          //for (TypeInfo ti : m.getTypeParameters()){
+            //  if (ti.asClassInfo() != null){
+                //System.out.println(" " +ti.asClassInfo().toString());
+              //} else {
+                //System.out.println(" null");
+              //}
+            //}
+          if (m.isHidden()) {
+              out.add(m);
+          }
+      }
+      return out.toArray(new MethodInfo[out.size()]);
+    }
+
+    /**
+     * Convert MethodDoc[] into MethodInfo[].  Also filters according
+     * to the -private, -public option, because the filtering doesn't seem
+     * to be working in the ClassDoc.constructors(boolean) call.
+     */
+    private static MethodInfo[] convertMethods(MethodDoc[] methods)
+    {
+        if (methods == null) return null;
+        ArrayList<MethodInfo> out = new ArrayList<MethodInfo>();
+        int N = methods.length;
+        for (int i=0; i<N; i++) {
+            MethodInfo m = Converter.obtainMethod(methods[i]);
+            //System.out.println(m.toString() + ": ");
+            //for (TypeInfo ti : m.getTypeParameters()){
+              //  if (ti.asClassInfo() != null){
+                  //System.out.println(" " +ti.asClassInfo().toString());
+                //} else {
+                  //System.out.println(" null");
+                //}
+              //}
+            if (m.checkLevel()) {
+                out.add(m);
+            }
+        }
+        return out.toArray(new MethodInfo[out.size()]);
+    }
+
+    private static MethodInfo[] convertMethods(ConstructorDoc[] methods)
+    {
+        if (methods == null) return null;
+        ArrayList<MethodInfo> out = new ArrayList<MethodInfo>();
+        int N = methods.length;
+        for (int i=0; i<N; i++) {
+            MethodInfo m = Converter.obtainMethod(methods[i]);
+            if (m.checkLevel()) {
+                out.add(m);
+            }
+        }
+        return out.toArray(new MethodInfo[out.size()]);
+    }
+    
+    private static MethodInfo[] convertNonWrittenConstructors(ConstructorDoc[] methods)
+    {
+        if (methods == null) return null;
+        ArrayList<MethodInfo> out = new ArrayList<MethodInfo>();
+        int N = methods.length;
+        for (int i=0; i<N; i++) {
+            MethodInfo m = Converter.obtainMethod(methods[i]);
+            if (!m.checkLevel()) {
+                out.add(m);
+            }
+        }
+        return out.toArray(new MethodInfo[out.size()]);
+    }
+
+    private static MethodInfo obtainMethod(MethodDoc o)
+    {
+        return (MethodInfo)mMethods.obtain(o);
+    }
+    private static MethodInfo obtainMethod(ConstructorDoc o)
+    {
+        return (MethodInfo)mMethods.obtain(o);
+    }
+    private static Cache mMethods = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            if (o instanceof AnnotationTypeElementDoc) {
+                AnnotationTypeElementDoc m = (AnnotationTypeElementDoc)o;
+                MethodInfo result = new MethodInfo(
+                                m.getRawCommentText(),
+                                Converter.convertTypes(m.typeParameters()),
+                                m.name(), m.signature(), 
+                                Converter.obtainClass(m.containingClass()),
+                                Converter.obtainClass(m.containingClass()),
+                                m.isPublic(), m.isProtected(),
+                                m.isPackagePrivate(), m.isPrivate(),
+                                m.isFinal(), m.isStatic(), m.isSynthetic(),
+                                m.isAbstract(), m.isSynchronized(), m.isNative(), true,
+                                "annotationElement",
+                                m.flatSignature(),
+                                Converter.obtainMethod(m.overriddenMethod()),
+                                Converter.obtainType(m.returnType()),
+                                Converter.convertParameters(m.parameters(), m),
+                                Converter.convertClasses(m.thrownExceptions()),
+                                Converter.convertSourcePosition(m.position()),
+                                Converter.convertAnnotationInstances(m.annotations())
+                            );
+                result.setVarargs(m.isVarArgs());
+                result.init(Converter.obtainAnnotationValue(m.defaultValue(), result));
+                return result;
+            }
+            else if (o instanceof MethodDoc) {
+                MethodDoc m = (MethodDoc)o;
+                MethodInfo result = new MethodInfo(
+                                m.getRawCommentText(),
+                                Converter.convertTypes(m.typeParameters()),
+                                m.name(), m.signature(), 
+                                Converter.obtainClass(m.containingClass()),
+                                Converter.obtainClass(m.containingClass()),
+                                m.isPublic(), m.isProtected(),
+                                m.isPackagePrivate(), m.isPrivate(),
+                                m.isFinal(), m.isStatic(), m.isSynthetic(),
+                                m.isAbstract(), m.isSynchronized(), m.isNative(), false,
+                                "method",
+                                m.flatSignature(),
+                                Converter.obtainMethod(m.overriddenMethod()),
+                                Converter.obtainType(m.returnType()),
+                                Converter.convertParameters(m.parameters(), m),
+                                Converter.convertClasses(m.thrownExceptions()),
+                                Converter.convertSourcePosition(m.position()),
+                                Converter.convertAnnotationInstances(m.annotations())
+                           );
+                result.setVarargs(m.isVarArgs());
+                result.init(null);
+                return result;
+            }
+            else {
+                ConstructorDoc m = (ConstructorDoc)o;
+                MethodInfo result = new MethodInfo(
+                                m.getRawCommentText(),
+                                Converter.convertTypes(m.typeParameters()),
+                                m.name(), m.signature(), 
+                                Converter.obtainClass(m.containingClass()),
+                                Converter.obtainClass(m.containingClass()),
+                                m.isPublic(), m.isProtected(),
+                                m.isPackagePrivate(), m.isPrivate(),
+                                m.isFinal(), m.isStatic(), m.isSynthetic(),
+                                false, m.isSynchronized(), m.isNative(), false,
+                                "constructor",
+                                m.flatSignature(),
+                                null,
+                                null,
+                                Converter.convertParameters(m.parameters(), m),
+                                Converter.convertClasses(m.thrownExceptions()),
+                                Converter.convertSourcePosition(m.position()),
+                                Converter.convertAnnotationInstances(m.annotations())
+                            );
+                result.setVarargs(m.isVarArgs());
+                result.init(null);
+                return result;
+            }
+        }
+    };
+
+
+    private static FieldInfo[] convertFields(FieldDoc[] fields)
+    {
+        if (fields == null) return null;
+        ArrayList<FieldInfo> out = new ArrayList<FieldInfo>();
+        int N = fields.length;
+        for (int i=0; i<N; i++) {
+            FieldInfo f = Converter.obtainField(fields[i]);
+            if (f.checkLevel()) {
+                out.add(f);
+            }
+        }
+        return out.toArray(new FieldInfo[out.size()]);
+    }
+
+    private static FieldInfo obtainField(FieldDoc o)
+    {
+        return (FieldInfo)mFields.obtain(o);
+    }
+    private static FieldInfo obtainField(ConstructorDoc o)
+    {
+        return (FieldInfo)mFields.obtain(o);
+    }
+    private static Cache mFields = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            FieldDoc f = (FieldDoc)o;
+            return new FieldInfo(f.name(),
+                            Converter.obtainClass(f.containingClass()),
+                            Converter.obtainClass(f.containingClass()),
+                            f.isPublic(), f.isProtected(),
+                            f.isPackagePrivate(), f.isPrivate(),
+                            f.isFinal(), f.isStatic(), f.isTransient(), f.isVolatile(),
+                            f.isSynthetic(),
+                            Converter.obtainType(f.type()),
+                            f.getRawCommentText(), f.constantValue(),
+                            Converter.convertSourcePosition(f.position()),
+                            Converter.convertAnnotationInstances(f.annotations())
+                        );
+        }
+    };
+
+    private static PackageInfo obtainPackage(PackageDoc o)
+    {
+        return (PackageInfo)mPackagees.obtain(o);
+    }
+    private static Cache mPackagees = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            PackageDoc p = (PackageDoc)o;
+            return new PackageInfo(p, p.name(),
+                    Converter.convertSourcePosition(p.position()));
+        }
+    };
+
+    private static TypeInfo obtainType(Type o)
+    {
+        return (TypeInfo)mTypes.obtain(o);
+    }
+    private static Cache mTypes = new Cache()
+    {
+       protected Object make(Object o)
+       {
+           Type t = (Type)o;
+           String simpleTypeName;
+           if (t instanceof ClassDoc) {
+               simpleTypeName = ((ClassDoc)t).name();
+           } else {
+               simpleTypeName = t.simpleTypeName();
+           }
+           TypeInfo ti = new TypeInfo(t.isPrimitive(), t.dimension(),
+                   simpleTypeName, t.qualifiedTypeName(),
+                   Converter.obtainClass(t.asClassDoc()));
+           return ti;
+       }
+        protected void made(Object o, Object r)
+        {
+            Type t = (Type)o;
+            TypeInfo ti = (TypeInfo)r;
+            if (t.asParameterizedType() != null) {
+                ti.setTypeArguments(Converter.convertTypes(
+                            t.asParameterizedType().typeArguments()));
+            }
+            else if (t instanceof ClassDoc) {
+                ti.setTypeArguments(Converter.convertTypes(((ClassDoc)t).typeParameters()));
+            }
+            else if (t.asTypeVariable() != null) {
+                ti.setBounds(null, Converter.convertTypes((t.asTypeVariable().bounds())));
+                ti.setIsTypeVariable(true);
+            }
+            else if (t.asWildcardType() != null) {
+                ti.setIsWildcard(true);
+                ti.setBounds(Converter.convertTypes(t.asWildcardType().superBounds()),
+                             Converter.convertTypes(t.asWildcardType().extendsBounds()));
+            }
+        }
+        protected Object keyFor(Object o)
+        {  
+            Type t = (Type)o;
+            String keyString = o.getClass().getName() + "/" + o.toString() + "/";
+            if (t.asParameterizedType() != null){
+              keyString += t.asParameterizedType().toString() +"/";
+              if (t.asParameterizedType().typeArguments() != null){
+              for(Type ty : t.asParameterizedType().typeArguments()){
+                keyString += ty.toString() + "/";
+              }
+              }
+            }else{
+              keyString += "NoParameterizedType//";
+            }
+            if (t.asTypeVariable() != null){
+              keyString += t.asTypeVariable().toString() +"/";
+              if (t.asTypeVariable().bounds() != null){
+              for(Type ty : t.asTypeVariable().bounds()){
+                keyString += ty.toString() + "/";
+              }
+              }
+            }else{
+              keyString += "NoTypeVariable//";
+            }
+            if (t.asWildcardType() != null){
+              keyString += t.asWildcardType().toString() +"/";
+              if (t.asWildcardType().superBounds() != null){
+              for(Type ty : t.asWildcardType().superBounds()){
+                keyString += ty.toString() + "/";
+              }
+              }
+              if (t.asWildcardType().extendsBounds() != null){
+                for(Type ty : t.asWildcardType().extendsBounds()){
+                  keyString += ty.toString() + "/";
+                }
+                }
+            }else{
+              keyString += "NoWildCardType//";
+            }
+            
+            
+            
+            return keyString;
+        }
+    };
+    
+
+
+    private static MemberInfo obtainMember(MemberDoc o)
+    {
+        return (MemberInfo)mMembers.obtain(o);
+    }
+    private static Cache mMembers = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            if (o instanceof MethodDoc) {
+                return Converter.obtainMethod((MethodDoc)o);
+            }
+            else if (o instanceof ConstructorDoc) {
+                return Converter.obtainMethod((ConstructorDoc)o);
+            }
+            else if (o instanceof FieldDoc) {
+                return Converter.obtainField((FieldDoc)o);
+            }
+            else {
+                return null;
+            }
+        }
+    };
+
+    private static AnnotationInstanceInfo[] convertAnnotationInstances(AnnotationDesc[] orig)
+    {
+        int len = orig.length;
+        AnnotationInstanceInfo[] out = new AnnotationInstanceInfo[len];
+        for (int i=0; i<len; i++) {
+            out[i] = Converter.obtainAnnotationInstance(orig[i]);
+        }
+        return out;
+    }
+
+
+    private static AnnotationInstanceInfo obtainAnnotationInstance(AnnotationDesc o)
+    {
+        return (AnnotationInstanceInfo)mAnnotationInstances.obtain(o);
+    }
+    private static Cache mAnnotationInstances = new Cache()
+    {
+        protected Object make(Object o)
+        {
+            AnnotationDesc a = (AnnotationDesc)o;
+            ClassInfo annotationType = Converter.obtainClass(a.annotationType());
+            AnnotationDesc.ElementValuePair[] ev = a.elementValues();
+            AnnotationValueInfo[] elementValues = new AnnotationValueInfo[ev.length];
+            for (int i=0; i<ev.length; i++) {
+                elementValues[i] = obtainAnnotationValue(ev[i].value(),
+                                            Converter.obtainMethod(ev[i].element()));
+            }
+            return new AnnotationInstanceInfo(annotationType, elementValues);
+        }
+    };
+
+
+    private abstract static class Cache
+    {
+        void put(Object key, Object value)
+        {
+            mCache.put(key, value);
+        }
+        Object obtain(Object o)
+        {
+            if (o == null ) {
+                return null;
+            }
+            Object k = keyFor(o);
+            Object r = mCache.get(k);
+            if (r == null) {
+                r = make(o);
+                mCache.put(k, r);
+                made(o, r);
+            }
+            return r;
+        }
+        protected HashMap<Object,Object> mCache = new HashMap<Object,Object>();
+        protected abstract Object make(Object o);
+        protected void made(Object o, Object r)
+        {
+        }
+        protected Object keyFor(Object o) { return o; }
+        Object[] all() { return null; }
+    }
+
+    // annotation values
+    private static HashMap<AnnotationValue,AnnotationValueInfo> mAnnotationValues = new HashMap();
+    private static HashSet<AnnotationValue> mAnnotationValuesNeedingInit = new HashSet();
+
+    private static AnnotationValueInfo obtainAnnotationValue(AnnotationValue o, MethodInfo element)
+    {
+        if (o == null) {
+            return null;
+        }
+        AnnotationValueInfo v = mAnnotationValues.get(o);
+        if (v != null) return v;
+        v = new AnnotationValueInfo(element);
+        mAnnotationValues.put(o, v);
+        if (mAnnotationValuesNeedingInit != null) {
+            mAnnotationValuesNeedingInit.add(o);
+        } else {
+            initAnnotationValue(o, v);
+        }
+        return v;
+    }
+
+    private static void initAnnotationValue(AnnotationValue o, AnnotationValueInfo v) {
+        Object orig = o.value();
+        Object converted;
+        if (orig instanceof Type) {
+            // class literal
+            converted = Converter.obtainType((Type)orig);
+        }
+        else if (orig instanceof FieldDoc) {
+            // enum constant
+            converted = Converter.obtainField((FieldDoc)orig);
+        }
+        else if (orig instanceof AnnotationDesc) {
+            // annotation instance
+            converted = Converter.obtainAnnotationInstance((AnnotationDesc)orig);
+        }
+        else if (orig instanceof AnnotationValue[]) {
+            AnnotationValue[] old = (AnnotationValue[])orig;
+            AnnotationValueInfo[] array = new AnnotationValueInfo[old.length];
+            for (int i=0; i<array.length; i++) {
+                array[i] = Converter.obtainAnnotationValue(old[i], null);
+            }
+            converted = array;
+        }
+        else {
+            converted = orig;
+        }
+        v.init(converted);
+    }
+
+    private static void finishAnnotationValueInit()
+    {
+        int depth = 0;
+        while (mAnnotationValuesNeedingInit.size() > 0) {
+            HashSet<AnnotationValue> set = mAnnotationValuesNeedingInit;
+            mAnnotationValuesNeedingInit = new HashSet();
+            for (AnnotationValue o: set) {
+                AnnotationValueInfo v = mAnnotationValues.get(o);
+                initAnnotationValue(o, v);
+            }
+            depth++;
+        }
+        mAnnotationValuesNeedingInit = null;
+    }
+}
diff --git a/tools/droiddoc/src/DocFile.java b/tools/droiddoc/src/DocFile.java
new file mode 100644
index 0000000..f53a35c
--- /dev/null
+++ b/tools/droiddoc/src/DocFile.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+
+public class DocFile
+{
+    private static final Pattern LINE = Pattern.compile("(.*)[\r]?\n",
+                                                        Pattern.MULTILINE);
+    private static final Pattern PROP = Pattern.compile("([^=]+)=(.*)");
+
+    public static String readFile(String filename)
+    {
+        try {
+            File f = new File(filename);
+            int length = (int)f.length();
+            FileReader reader = new FileReader(f);
+            char[] buf = new char[length];
+            int index = 0;
+            int amt;
+            while (true) {
+                amt = reader.read(buf, index, length-index);
+
+                if (amt < 1) {
+                    break;
+                }
+
+                index += amt;
+            }
+            return new String(buf, 0, index);
+        }
+        catch (IOException e) {
+            return null;
+        }
+    }
+
+    public static void writePage(String docfile, String relative,
+                                    String outfile)
+    {
+        HDF hdf = DroidDoc.makeHDF();
+
+        /*
+        System.out.println("docfile='" + docfile
+                            + "' relative='" + relative + "'"
+                            + "' outfile='" + outfile + "'");
+        */
+
+        String filedata = readFile(docfile);
+
+        // The document is properties up until the line "@jd:body".
+        // Any blank lines are ignored.
+        int start = -1;
+        int lineno = 1;
+        Matcher lines = LINE.matcher(filedata);
+        String line = null;
+        while (lines.find()) {
+            line = lines.group(1);
+            if (line.length() > 0) {
+                if (line.equals("@jd:body")) {
+                    start = lines.end();
+                    break;
+                }
+                Matcher prop = PROP.matcher(line);
+                if (prop.matches()) {
+                    String key = prop.group(1);
+                    String value = prop.group(2);
+                    hdf.setValue(key, value);
+                } else {
+                    break;
+                }
+            }
+            lineno++;
+        }
+        if (start < 0) {
+            System.err.println(docfile + ":" + lineno + ": error parsing docfile");
+            if (line != null) {
+                System.err.println(docfile + ":" + lineno + ":" + line);
+            }
+            System.exit(1);
+        }
+
+        // if they asked to only be for a certain template, maybe skip it
+        String fromTemplate = hdf.getValue("template.which", "");
+        String fromPage = hdf.getValue("page.onlyfortemplate", "");
+        if (!"".equals(fromPage) && !fromTemplate.equals(fromPage)) {
+            return;
+        }
+
+        // and the actual text after that
+        String commentText = filedata.substring(start);
+
+        Comment comment = new Comment(commentText, null,
+                                    new SourcePositionInfo(docfile, lineno, 1));
+        TagInfo[] tags = comment.tags();
+
+        TagInfo.makeHDF(hdf, "root.descr", tags);
+
+        hdf.setValue("commentText", commentText);
+        
+        if (outfile.indexOf("sdk/") != -1) {
+            hdf.setValue("sdk", "true");
+            if (outfile.indexOf("index.html") != -1) {
+                ClearPage.write(hdf, "sdkpage.cs", outfile);
+            } else {
+                ClearPage.write(hdf, "docpage.cs", outfile);
+            }
+        } else if (outfile.indexOf("guide/") != -1){
+            hdf.setValue("guide", "true");
+            ClearPage.write(hdf, "docpage.cs", outfile);
+        } else if (outfile.indexOf("publish/") != -1){
+            hdf.setValue("publish", "true");
+            ClearPage.write(hdf, "docpage.cs", outfile);
+        } else {
+            ClearPage.write(hdf, "nosidenavpage.cs", outfile);
+        }
+    }
+
+}
diff --git a/tools/droiddoc/src/DocInfo.java b/tools/droiddoc/src/DocInfo.java
new file mode 100644
index 0000000..2530dc2
--- /dev/null
+++ b/tools/droiddoc/src/DocInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public abstract class DocInfo
+{
+    public DocInfo(String rawCommentText, SourcePositionInfo sp)
+    {
+        mRawCommentText = rawCommentText;
+        mPosition = sp;
+    }
+
+    public boolean isHidden()
+    {
+        return comment().isHidden();
+    }
+
+    public boolean isDocOnly() {
+        return comment().isDocOnly();
+    }
+    
+    public String getRawCommentText()
+    {
+        return mRawCommentText;
+    }
+
+    public Comment comment()
+    {
+        if (mComment == null) {
+            mComment = new Comment(mRawCommentText, parent(), mPosition);
+        }
+        return mComment;
+    }
+
+    public SourcePositionInfo position()
+    {
+        return mPosition;
+    }
+
+    public abstract ContainerInfo parent();
+
+    private String mRawCommentText;
+    Comment mComment;
+    SourcePositionInfo mPosition;
+}
+
diff --git a/tools/droiddoc/src/DroidDoc.java b/tools/droiddoc/src/DroidDoc.java
new file mode 100644
index 0000000..f664c41
--- /dev/null
+++ b/tools/droiddoc/src/DroidDoc.java
@@ -0,0 +1,1342 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.sun.javadoc.*;
+
+import org.clearsilver.HDF;
+
+import java.util.*;
+import java.io.*;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class DroidDoc
+{
+    private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant";
+    private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION = "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION";
+    private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION = "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION";
+    private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION = "android.annotation.SdkConstant.SdkConstantType.SERVICE_INTENT_ACTION";
+    private static final String SDK_CONSTANT_TYPE_CATEGORY = "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY";
+    private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget";
+    private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout";
+
+    private static final int TYPE_NONE = 0;
+    private static final int TYPE_WIDGET = 1;
+    private static final int TYPE_LAYOUT = 2;
+    private static final int TYPE_LAYOUT_PARAM = 3;
+    
+    public static final int SHOW_PUBLIC = 0x00000001;
+    public static final int SHOW_PROTECTED = 0x00000003;
+    public static final int SHOW_PACKAGE = 0x00000007;
+    public static final int SHOW_PRIVATE = 0x0000000f;
+    public static final int SHOW_HIDDEN = 0x0000001f;
+
+    public static int showLevel = SHOW_PROTECTED;
+
+    public static final String javadocDir = "reference/";
+    public static String htmlExtension;
+
+    public static RootDoc root;
+    public static ArrayList<String[]> mHDFData = new ArrayList<String[]>();
+    public static Map<Character,String> escapeChars = new HashMap<Character,String>();
+    public static String title = "";
+
+    public static boolean checkLevel(int level)
+    {
+        return (showLevel & level) == level;
+    }
+
+    public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp,
+            boolean priv, boolean hidden)
+    {
+        int level = 0;
+        if (hidden && !checkLevel(SHOW_HIDDEN)) {
+            return false;
+        }
+        if (pub && checkLevel(SHOW_PUBLIC)) {
+            return true;
+        }
+        if (prot && checkLevel(SHOW_PROTECTED)) {
+            return true;
+        }
+        if (pkgp && checkLevel(SHOW_PACKAGE)) {
+            return true;
+        }
+        if (priv && checkLevel(SHOW_PRIVATE)) {
+            return true;
+        }
+        return false;
+    }
+    
+    public static boolean start(RootDoc r)
+    {
+        String keepListFile = null;
+        String proofreadFile = null;
+        String todoFile = null;
+        String sdkValuePath = null;
+        ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>();
+        String stubsDir = null;
+        //Create the dependency graph for the stubs directory
+        boolean apiXML = false;
+        String apiFile = null;
+        String debugStubsFile = "";
+        HashSet<String> stubPackages = null;
+
+        root = r;
+
+        String[][] options = r.options();
+        for (String[] a: options) {
+            if (a[0].equals("-d")) {
+                ClearPage.outputDir = a[1];
+            }
+            else if (a[0].equals("-templatedir")) {
+                ClearPage.addTemplateDir(a[1]);
+            }
+            else if (a[0].equals("-hdf")) {
+                mHDFData.add(new String[] {a[1], a[2]});
+            }
+            else if (a[0].equals("-toroot")) {
+                ClearPage.toroot = a[1];
+            }
+            else if (a[0].equals("-samplecode")) {
+                sampleCodes.add(new SampleCode(a[1], a[2], a[3]));
+            }
+            else if (a[0].equals("-htmldir")) {
+                ClearPage.htmlDir = a[1];
+            }
+            else if (a[0].equals("-title")) {
+                DroidDoc.title = a[1];
+            }
+            else if (a[0].equals("-werror")) {
+                Errors.setWarningsAreErrors(true);
+            }
+            else if (a[0].equals("-error") || a[0].equals("-warning")
+                    || a[0].equals("-hide")) {
+                try {
+                    int level = -1;
+                    if (a[0].equals("-error")) {
+                        level = Errors.ERROR;
+                    }
+                    else if (a[0].equals("-warning")) {
+                        level = Errors.WARNING;
+                    }
+                    else if (a[0].equals("-hide")) {
+                        level = Errors.HIDDEN;
+                    }
+                    Errors.setErrorLevel(Integer.parseInt(a[1]), level);
+                }
+                catch (NumberFormatException e) {
+                    // already printed below
+                    return false;
+                }
+            }
+            else if (a[0].equals("-keeplist")) {
+                keepListFile = a[1];
+            }
+            else if (a[0].equals("-proofread")) {
+                proofreadFile = a[1];
+            }
+            else if (a[0].equals("-todo")) {
+                todoFile = a[1];
+            }
+            else if (a[0].equals("-public")) {
+                showLevel = SHOW_PUBLIC;
+            }
+            else if (a[0].equals("-protected")) {
+                showLevel = SHOW_PROTECTED;
+            }
+            else if (a[0].equals("-package")) {
+                showLevel = SHOW_PACKAGE;
+            }
+            else if (a[0].equals("-private")) {
+                showLevel = SHOW_PRIVATE;
+            }
+            else if (a[0].equals("-hidden")) {
+                showLevel = SHOW_HIDDEN;
+            }
+            else if (a[0].equals("-stubs")) {
+                stubsDir = a[1];
+            }
+            else if (a[0].equals("-stubpackages")) {
+                stubPackages = new HashSet();
+                for (String pkg: a[1].split(":")) {
+                    stubPackages.add(pkg);
+                }
+            }
+            else if (a[0].equals("-sdkvalues")) {
+                sdkValuePath = a[1];
+            }
+            else if (a[0].equals("-apixml")) {
+                apiXML = true;
+                apiFile = a[1];
+            }
+        }
+
+        // read some prefs from the template
+        if (!readTemplateSettings()) {
+            return false;
+        }
+
+        // Set up the data structures
+        Converter.makeInfo(r);
+
+        // Files for proofreading
+        if (proofreadFile != null) {
+            Proofread.initProofread(proofreadFile);
+        }
+        if (todoFile != null) {
+            TodoFile.writeTodoFile(todoFile);
+        }
+
+        // HTML Pages
+        if (ClearPage.htmlDir != null) {
+            writeHTMLPages();
+        }
+
+        // Navigation tree
+        NavTree.writeNavTree(javadocDir);
+
+        // Packages Pages
+        writePackages(javadocDir
+                        + (ClearPage.htmlDir!=null
+                            ? "packages" + htmlExtension
+                            : "index" + htmlExtension));
+
+        // Classes
+        writeClassLists();
+        writeClasses();
+        writeHierarchy();
+ //      writeKeywords();
+
+        // Lists for JavaScript
+        writeLists();
+        if (keepListFile != null) {
+            writeKeepList(keepListFile);
+        }
+
+        // Sample Code
+        for (SampleCode sc: sampleCodes) {
+            sc.write();
+        }
+
+        // Index page
+        writeIndex();
+
+        Proofread.finishProofread(proofreadFile);
+
+        // Stubs
+        if (stubsDir != null) {
+            Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages);
+        }
+        
+        if (sdkValuePath != null) {
+            writeSdkValues(sdkValuePath);
+        }
+
+        Errors.printErrors();
+        return !Errors.hadError;
+    }
+
+    private static void writeIndex() {
+        HDF data = makeHDF();
+        ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension);
+    }
+
+    private static boolean readTemplateSettings()
+    {
+        HDF data = makeHDF();
+        htmlExtension = data.getValue("template.extension", ".html");
+        int i=0;
+        while (true) {
+            String k = data.getValue("template.escape." + i + ".key", "");
+            String v = data.getValue("template.escape." + i + ".value", "");
+            if ("".equals(k)) {
+                break;
+            }
+            if (k.length() != 1) {
+                System.err.println("template.escape." + i + ".key must have a length of 1: " + k);
+                return false;
+            }
+            escapeChars.put(k.charAt(0), v);
+            i++;
+        }
+        return true;
+    }
+
+    public static String escape(String s) {
+        if (escapeChars.size() == 0) {
+            return s;
+        }
+        StringBuffer b = null;
+        int begin = 0;
+        final int N = s.length();
+        for (int i=0; i<N; i++) {
+            char c = s.charAt(i);
+            String mapped = escapeChars.get(c);
+            if (mapped != null) {
+                if (b == null) {
+                    b = new StringBuffer(s.length() + mapped.length());
+                }
+                if (begin != i) {
+                    b.append(s.substring(begin, i));
+                }
+                b.append(mapped);
+                begin = i+1;
+            }
+        }
+        if (b != null) {
+            if (begin != N) {
+                b.append(s.substring(begin, N));
+            }
+            return b.toString();
+        }
+        return s;
+    }
+
+    public static void setPageTitle(HDF data, String title)
+    {
+        String s = title;
+        if (DroidDoc.title.length() > 0) {
+            s += " - " + DroidDoc.title;
+        }
+        data.setValue("page.title", s);
+    }
+
+    public static LanguageVersion languageVersion()
+    {
+        return LanguageVersion.JAVA_1_5;
+    }
+
+    public static int optionLength(String option)
+    {
+        if (option.equals("-d")) {
+            return 2;
+        }
+        if (option.equals("-templatedir")) {
+            return 2;
+        }
+        if (option.equals("-hdf")) {
+            return 3;
+        }
+        if (option.equals("-toroot")) {
+            return 2;
+        }
+        if (option.equals("-samplecode")) {
+            return 4;
+        }
+        if (option.equals("-htmldir")) {
+            return 2;
+        }
+        if (option.equals("-title")) {
+            return 2;
+        }
+        if (option.equals("-werror")) {
+            return 1;
+        }
+        if (option.equals("-hide")) {
+            return 2;
+        }
+        if (option.equals("-warning")) {
+            return 2;
+        }
+        if (option.equals("-error")) {
+            return 2;
+        }
+        if (option.equals("-keeplist")) {
+            return 2;
+        }
+        if (option.equals("-proofread")) {
+            return 2;
+        }
+        if (option.equals("-todo")) {
+            return 2;
+        }
+        if (option.equals("-public")) {
+            return 1;
+        }
+        if (option.equals("-protected")) {
+            return 1;
+        }
+        if (option.equals("-package")) {
+            return 1;
+        }
+        if (option.equals("-private")) {
+            return 1;
+        }
+        if (option.equals("-hidden")) {
+            return 1;
+        }
+        if (option.equals("-stubs")) {
+            return 2;
+        }
+        if (option.equals("-stubpackages")) {
+            return 2;
+        }
+        if (option.equals("-sdkvalues")) {
+            return 2;
+        }
+        if (option.equals("-apixml")) {
+            return 2;
+        }
+        return 0;
+    }
+    
+    public static boolean validOptions(String[][] options, DocErrorReporter r)
+    {
+        for (String[] a: options) {
+            if (a[0].equals("-error") || a[0].equals("-warning")
+                    || a[0].equals("-hide")) {
+                try {
+                    Integer.parseInt(a[1]);
+                }
+                catch (NumberFormatException e) {
+                    r.printError("bad -" + a[0] + " value must be a number: "
+                            + a[1]);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public static HDF makeHDF()
+    {
+        HDF data = new HDF();
+
+        for (String[] p: mHDFData) {
+            data.setValue(p[0], p[1]);
+        }
+
+        try {
+            for (String p: ClearPage.hdfFiles) {
+                data.readFile(p);
+            }
+        }
+        catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return data;
+    }
+
+    public static HDF makePackageHDF()
+    {
+        HDF data = makeHDF();
+        ClassInfo[] classes = Converter.rootClasses();
+
+        SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
+        for (ClassInfo cl: classes) {
+            PackageInfo pkg = cl.containingPackage();
+            String name;
+            if (pkg == null) {
+                name = "";
+            } else {
+                name = pkg.name();
+            }
+            sorted.put(name, pkg);
+        }
+
+        int i = 0;
+        for (String s: sorted.keySet()) {
+            PackageInfo pkg = sorted.get(s);
+
+            if (pkg.isHidden()) {
+                continue;
+            }
+            Boolean allHidden = true;
+            int pass = 0;
+            ClassInfo[] classesToCheck = null;
+            while (pass < 5 ) {
+                switch(pass) {
+                case 0:
+                    classesToCheck = pkg.ordinaryClasses();
+                    break;
+                case 1:
+                    classesToCheck = pkg.enums();
+                    break;
+                case 2:
+                    classesToCheck = pkg.errors();
+                    break;
+                case 3:
+                    classesToCheck = pkg.exceptions();
+                    break;
+                case 4:
+                    classesToCheck = pkg.interfaces();
+                    break;
+                default:
+                    System.err.println("Error reading package: " + pkg.name());
+                    break;
+                }
+                for (ClassInfo cl : classesToCheck) {
+                    if (!cl.isHidden()) {
+                        allHidden = false;
+                        break;
+                    }
+                }
+                if (!allHidden) {
+                    break;
+                }
+                pass++;
+            }
+            if (allHidden) {
+                continue;
+            }
+
+            data.setValue("reference", "true");
+            data.setValue("docs.packages." + i + ".name", s);
+            data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
+            TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr",
+                                               pkg.firstSentenceTags());
+            i++;
+        }
+
+        return data;
+    }
+
+    public static void writeDirectory(File dir, String relative)
+    {
+        File[] files = dir.listFiles();
+        int i, count = files.length;
+        for (i=0; i<count; i++) {
+            File f = files[i];
+            if (f.isFile()) {
+                String templ = relative + f.getName();
+                int len = templ.length();
+                if (len > 3 && ".cs".equals(templ.substring(len-3))) {
+                    HDF data = makeHDF();
+                    String filename = templ.substring(0,len-3) + htmlExtension;
+                    ClearPage.write(data, templ, filename);
+                }
+                else if (len > 3 && ".jd".equals(templ.substring(len-3))) {
+                    String filename = templ.substring(0,len-3) + htmlExtension;
+                    DocFile.writePage(f.getAbsolutePath(), relative, filename);
+                }
+                else {
+                    ClearPage.copyFile(f, templ);
+                }
+            }
+            else if (f.isDirectory()) {
+                writeDirectory(f, relative + f.getName() + "/");
+            }
+        }
+    }
+
+    public static void writeHTMLPages()
+    {
+        File f = new File(ClearPage.htmlDir);
+        if (!f.isDirectory()) {
+            System.err.println("htmlDir not a directory: " + ClearPage.htmlDir);
+        }
+        writeDirectory(f, "");
+    }
+
+    public static void writeLists()
+    {
+        HDF data = makeHDF();
+
+        ClassInfo[] classes = Converter.rootClasses();
+
+        SortedMap<String, Object> sorted = new TreeMap<String, Object>();
+        for (ClassInfo cl: classes) {
+            if (cl.isHidden()) {
+                continue;
+            }
+            sorted.put(cl.qualifiedName(), cl);
+            PackageInfo pkg = cl.containingPackage();
+            String name;
+            if (pkg == null) {
+                name = "";
+            } else {
+                name = pkg.name();
+            }
+            sorted.put(name, pkg);
+        }
+
+        int i = 0;
+        for (String s: sorted.keySet()) {
+            data.setValue("docs.pages." + i + ".id" , ""+i);
+            data.setValue("docs.pages." + i + ".label" , s);
+
+            Object o = sorted.get(s);
+            if (o instanceof PackageInfo) {
+                PackageInfo pkg = (PackageInfo)o;
+                data.setValue("docs.pages." + i + ".link" , pkg.htmlPage());
+                data.setValue("docs.pages." + i + ".type" , "package");
+            }
+            else if (o instanceof ClassInfo) {
+                ClassInfo cl = (ClassInfo)o;
+                data.setValue("docs.pages." + i + ".link" , cl.htmlPage());
+                data.setValue("docs.pages." + i + ".type" , "class");
+            }
+            i++;
+        }
+
+        ClearPage.write(data, "lists.cs", javadocDir + "lists.js");
+    }
+
+    public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) {
+        if (!notStrippable.add(cl)) {
+            // slight optimization: if it already contains cl, it already contains
+            // all of cl's parents
+            return;
+        }
+        ClassInfo supr = cl.superclass();
+        if (supr != null) {
+            cantStripThis(supr, notStrippable);
+        }
+        for (ClassInfo iface: cl.interfaces()) {
+            cantStripThis(iface, notStrippable);
+        }
+    }
+
+    private static String getPrintableName(ClassInfo cl) {
+        ClassInfo containingClass = cl.containingClass();
+        if (containingClass != null) {
+            // This is an inner class.
+            String baseName = cl.name();
+            baseName = baseName.substring(baseName.lastIndexOf('.') + 1);
+            return getPrintableName(containingClass) + '$' + baseName;
+        }
+        return cl.qualifiedName();
+    }
+
+    /**
+     * Writes the list of classes that must be present in order to
+     * provide the non-hidden APIs known to javadoc.
+     *
+     * @param filename the path to the file to write the list to
+     */
+    public static void writeKeepList(String filename) {
+        HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
+        ClassInfo[] all = Converter.allClasses();
+        Arrays.sort(all); // just to make the file a little more readable
+
+        // If a class is public and not hidden, then it and everything it derives
+        // from cannot be stripped.  Otherwise we can strip it.
+        for (ClassInfo cl: all) {
+            if (cl.isPublic() && !cl.isHidden()) {
+                cantStripThis(cl, notStrippable);
+            }
+        }
+        PrintStream stream = null;
+        try {
+            stream = new PrintStream(filename);
+            for (ClassInfo cl: notStrippable) {
+                stream.println(getPrintableName(cl));
+            }
+        }
+        catch (FileNotFoundException e) {
+            System.err.println("error writing file: " + filename);
+        }
+        finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+    }
+
+    private static PackageInfo[] sVisiblePackages = null;
+    public static PackageInfo[] choosePackages() {
+        if (sVisiblePackages != null) {
+            return sVisiblePackages;
+        }
+
+        ClassInfo[] classes = Converter.rootClasses();
+        SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
+        for (ClassInfo cl: classes) {
+            PackageInfo pkg = cl.containingPackage();
+            String name;
+            if (pkg == null) {
+                name = "";
+            } else {
+                name = pkg.name();
+            }
+            sorted.put(name, pkg);
+        }
+
+        ArrayList<PackageInfo> result = new ArrayList();
+
+        for (String s: sorted.keySet()) {
+            PackageInfo pkg = sorted.get(s);
+
+            if (pkg.isHidden()) {
+                continue;
+            }
+            Boolean allHidden = true;
+            int pass = 0;
+            ClassInfo[] classesToCheck = null;
+            while (pass < 5 ) {
+                switch(pass) {
+                case 0:
+                    classesToCheck = pkg.ordinaryClasses();
+                    break;
+                case 1:
+                    classesToCheck = pkg.enums();
+                    break;
+                case 2:
+                    classesToCheck = pkg.errors();
+                    break;
+                case 3:
+                    classesToCheck = pkg.exceptions();
+                    break;
+                case 4:
+                    classesToCheck = pkg.interfaces();
+                    break;
+                default:
+                    System.err.println("Error reading package: " + pkg.name());
+                    break;
+                }
+                for (ClassInfo cl : classesToCheck) {
+                    if (!cl.isHidden()) {
+                        allHidden = false;
+                        break;
+                    }
+                }
+                if (!allHidden) {
+                    break;
+                }
+                pass++;
+            }
+            if (allHidden) {
+                continue;
+            }
+
+            result.add(pkg);
+        }
+
+        sVisiblePackages = result.toArray(new PackageInfo[result.size()]);
+        return sVisiblePackages;
+    }
+
+    public static void writePackages(String filename)
+    {
+        HDF data = makePackageHDF();
+
+        int i = 0;
+        for (PackageInfo pkg: choosePackages()) {
+            writePackage(pkg);
+
+            data.setValue("docs.packages." + i + ".name", pkg.name());
+            data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
+            TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr",
+                            pkg.firstSentenceTags());
+
+            i++;
+        }
+
+        setPageTitle(data, "Package Index");
+
+        TagInfo.makeHDF(data, "root.descr",
+                Converter.convertTags(root.inlineTags(), null));
+
+        ClearPage.write(data, "packages.cs", filename);
+        ClearPage.write(data, "package-list.cs", javadocDir + "package-list");
+
+        Proofread.writePackages(filename,
+                Converter.convertTags(root.inlineTags(), null));
+    }
+
+    public static void writePackage(PackageInfo pkg)
+    {
+        // these this and the description are in the same directory,
+        // so it's okay
+        HDF data = makePackageHDF();
+
+        String name = pkg.name();
+
+        data.setValue("package.name", name);
+        data.setValue("package.descr", "...description...");
+
+        makeClassListHDF(data, "package.interfaces", 
+                         ClassInfo.sortByName(pkg.interfaces()));
+        makeClassListHDF(data, "package.classes",
+                         ClassInfo.sortByName(pkg.ordinaryClasses()));
+        makeClassListHDF(data, "package.enums",
+                         ClassInfo.sortByName(pkg.enums()));
+        makeClassListHDF(data, "package.exceptions",
+                         ClassInfo.sortByName(pkg.exceptions()));
+        makeClassListHDF(data, "package.errors",
+                         ClassInfo.sortByName(pkg.errors()));
+        TagInfo.makeHDF(data, "package.shortDescr",
+                         pkg.firstSentenceTags());
+        TagInfo.makeHDF(data, "package.descr", pkg.inlineTags());
+
+        String filename = pkg.htmlPage();
+        setPageTitle(data, name);
+        ClearPage.write(data, "package.cs", filename);
+
+        filename = pkg.fullDescriptionHtmlPage();
+        setPageTitle(data, name + " Details");
+        ClearPage.write(data, "package-descr.cs", filename);
+
+        Proofread.writePackage(filename, pkg.inlineTags());
+    }
+
+    public static void writeClassLists()
+    {
+        int i;
+        HDF data = makePackageHDF();
+
+        ClassInfo[] classes = PackageInfo.filterHidden(
+                                    Converter.convertClasses(root.classes()));
+        if (classes.length == 0) {
+            return ;
+        }
+
+        Sorter[] sorted = new Sorter[classes.length];
+        for (i=0; i<sorted.length; i++) {
+            ClassInfo cl = classes[i];
+            String name = cl.name();
+            sorted[i] = new Sorter(name, cl);
+        }
+
+        Arrays.sort(sorted);
+
+        // make a pass and resolve ones that have the same name
+        int firstMatch = 0;
+        String lastName = sorted[0].label;
+        for (i=1; i<sorted.length; i++) {
+            String s = sorted[i].label;
+            if (!lastName.equals(s)) {
+                if (firstMatch != i-1) {
+                    // there were duplicates
+                    for (int j=firstMatch; j<i; j++) {
+                        PackageInfo pkg = ((ClassInfo)sorted[j].data).containingPackage();
+                        if (pkg != null) {
+                            sorted[j].label = sorted[j].label + " (" + pkg.name() + ")";
+                        }
+                    }
+                }
+                firstMatch = i;
+                lastName = s;
+            }
+        }
+
+        // and sort again
+        Arrays.sort(sorted);
+
+        for (i=0; i<sorted.length; i++) {
+            String s = sorted[i].label;
+            ClassInfo cl = (ClassInfo)sorted[i].data;
+            char first = Character.toUpperCase(s.charAt(0));
+            cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i);
+        }
+
+        setPageTitle(data, "Class Index");
+        ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension);
+    }
+
+    // we use the word keywords because "index" means something else in html land
+    // the user only ever sees the word index
+/*    public static void writeKeywords()
+    {
+        ArrayList<KeywordEntry> keywords = new ArrayList<KeywordEntry>();
+
+        ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes()));
+
+        for (ClassInfo cl: classes) {
+            cl.makeKeywordEntries(keywords);
+        }
+
+        HDF data = makeHDF();
+
+        Collections.sort(keywords);
+        
+        int i=0;
+        for (KeywordEntry entry: keywords) {
+            String base = "keywords." + entry.firstChar() + "." + i;
+            entry.makeHDF(data, base);
+            i++;
+        }
+
+        setPageTitle(data, "Index");
+        ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + htmlExtension);
+    } */
+
+    public static void writeHierarchy()
+    {
+        ClassInfo[] classes = Converter.rootClasses();
+        ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
+        for (ClassInfo cl: classes) {
+            if (!cl.isHidden()) {
+                info.add(cl);
+            }
+        }
+        HDF data = makePackageHDF();
+        Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()]));
+        setPageTitle(data, "Class Hierarchy");
+        ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension);
+    }
+
+    public static void writeClasses()
+    {
+        ClassInfo[] classes = Converter.rootClasses();
+
+        for (ClassInfo cl: classes) {
+            HDF data = makePackageHDF();
+            if (!cl.isHidden()) {
+                writeClass(cl, data);
+            }
+        }
+    }
+
+    public static void writeClass(ClassInfo cl, HDF data)
+    {
+        cl.makeHDF(data);
+
+        setPageTitle(data, cl.name());
+        ClearPage.write(data, "class.cs", cl.htmlPage());
+
+        Proofread.writeClass(cl.htmlPage(), cl);
+    }
+
+    public static void makeClassListHDF(HDF data, String base,
+            ClassInfo[] classes)
+    {
+        for (int i=0; i<classes.length; i++) {
+            ClassInfo cl = classes[i];
+            if (!cl.isHidden()) {
+                cl.makeShortDescrHDF(data, base + "." + i);
+            }
+        }
+    }
+
+    public static String linkTarget(String source, String target)
+    {
+        String[] src = source.split("/");
+        String[] tgt = target.split("/");
+
+        int srclen = src.length;
+        int tgtlen = tgt.length;
+
+        int same = 0;
+        while (same < (srclen-1)
+                && same < (tgtlen-1)
+                && (src[same].equals(tgt[same]))) {
+            same++;
+        }
+
+        String s = "";
+
+        int up = srclen-same-1;
+        for (int i=0; i<up; i++) {
+            s += "../";
+        }
+
+
+        int N = tgtlen-1;
+        for (int i=same; i<N; i++) {
+            s += tgt[i] + '/';
+        }
+        s += tgt[tgtlen-1];
+
+        return s;
+    }
+
+    /**
+     * Returns true if the given element has an @hide annotation.
+     */
+    private static boolean hasHideAnnotation(Doc doc) {
+        return doc.getRawCommentText().indexOf("@hide") != -1;
+    }
+
+    /**
+     * Returns true if the given element is hidden.
+     */
+    private static boolean isHidden(Doc doc) {
+        // Methods, fields, constructors.
+        if (doc instanceof MemberDoc) {
+            return hasHideAnnotation(doc);
+        }
+
+        // Classes, interfaces, enums, annotation types.
+        if (doc instanceof ClassDoc) {
+            ClassDoc classDoc = (ClassDoc) doc;
+
+            // Check the containing package.
+            if (hasHideAnnotation(classDoc.containingPackage())) {
+                return true;
+            }
+
+            // Check the class doc and containing class docs if this is a
+            // nested class.
+            ClassDoc current = classDoc;
+            do {
+                if (hasHideAnnotation(current)) {
+                    return true;
+                }
+
+                current = current.containingClass();
+            } while (current != null);
+        }
+
+        return false;
+    }
+
+    /**
+     * Filters out hidden elements.
+     */
+    private static Object filterHidden(Object o, Class<?> expected) {
+        if (o == null) {
+            return null;
+        }
+
+        Class type = o.getClass();
+        if (type.getName().startsWith("com.sun.")) {
+            // TODO: Implement interfaces from superclasses, too.
+            return Proxy.newProxyInstance(type.getClassLoader(),
+                    type.getInterfaces(), new HideHandler(o));
+        } else if (o instanceof Object[]) {
+            Class<?> componentType = expected.getComponentType();
+            Object[] array = (Object[]) o;
+            List<Object> list = new ArrayList<Object>(array.length);
+            for (Object entry : array) {
+                if ((entry instanceof Doc) && isHidden((Doc) entry)) {
+                    continue;
+                }
+                list.add(filterHidden(entry, componentType));
+            }
+            return list.toArray(
+                    (Object[]) Array.newInstance(componentType, list.size()));
+        } else {
+            return o;
+        }
+    }
+
+    /**
+     * Filters hidden elements out of method return values.
+     */
+    private static class HideHandler implements InvocationHandler {
+
+        private final Object target;
+
+        public HideHandler(Object target) {
+            this.target = target;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            String methodName = method.getName();
+            if (args != null) {
+                if (methodName.equals("compareTo") ||
+                    methodName.equals("equals") ||
+                    methodName.equals("overrides") ||
+                    methodName.equals("subclassOf")) {
+                    args[0] = unwrap(args[0]);
+                }
+            }
+
+            if (methodName.equals("getRawCommentText")) {
+                return filterComment((String) method.invoke(target, args));
+            }
+            
+            // escape "&" in disjunctive types.
+            if (proxy instanceof Type && methodName.equals("toString")) {
+                return ((String) method.invoke(target, args))
+                        .replace("&", "&amp;");
+            }
+
+            try {
+                return filterHidden(method.invoke(target, args),
+                        method.getReturnType());
+            } catch (InvocationTargetException e) {
+                throw e.getTargetException();
+            }
+        }
+
+        private String filterComment(String s) {
+            if (s == null) {
+                return null;
+            }
+
+            s = s.trim();
+
+            // Work around off by one error
+            while (s.length() >= 5
+                    && s.charAt(s.length() - 5) == '{') {
+                s += "&nbsp;";
+            }
+
+            return s;
+        }
+
+        private static Object unwrap(Object proxy) {
+            if (proxy instanceof Proxy)
+                return ((HideHandler)Proxy.getInvocationHandler(proxy)).target;
+            return proxy;
+        }
+    }
+
+    public static String scope(Scoped scoped) {
+        if (scoped.isPublic()) {
+            return "public";
+        }
+        else if (scoped.isProtected()) {
+            return "protected";
+        }
+        else if (scoped.isPackagePrivate()) {
+            return "";
+        }
+        else if (scoped.isPrivate()) {
+            return "private";
+        }
+        else {
+            throw new RuntimeException("invalid scope for object " + scoped);
+        }
+    }
+    
+    /**
+     * Collect the values used by the Dev tools and write them in files packaged with the SDK
+     * @param output the ouput directory for the files.
+     */
+    private static void writeSdkValues(String output) {
+        ArrayList<String> activityActions = new ArrayList<String>();
+        ArrayList<String> broadcastActions = new ArrayList<String>();
+        ArrayList<String> serviceActions = new ArrayList<String>();
+        ArrayList<String> categories = new ArrayList<String>();
+        
+        ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
+        ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
+        ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
+        
+        ClassInfo[] classes = Converter.allClasses();
+
+        // Go through all the fields of all the classes, looking SDK stuff.
+        for (ClassInfo clazz : classes) {
+            
+            // first check constant fields for the SdkConstant annotation.
+            FieldInfo[] fields = clazz.allSelfFields();
+            for (FieldInfo field : fields) {
+                Object cValue = field.constantValue();
+                if (cValue != null) {
+                    AnnotationInstanceInfo[] annotations = field.annotations();
+                    if (annotations.length > 0) {
+                        for (AnnotationInstanceInfo annotation : annotations) {
+                            if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
+                                AnnotationValueInfo[] values = annotation.elementValues();
+                                if (values.length > 0) {
+                                    String type = values[0].valueString();
+                                    if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
+                                        activityActions.add(cValue.toString());
+                                    } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
+                                        broadcastActions.add(cValue.toString());
+                                    } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
+                                        serviceActions.add(cValue.toString());
+                                    } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
+                                        categories.add(cValue.toString());
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            
+            // Now check the class for @Widget or if its in the android.widget package
+            // (unless the class is hidden or abstract, or non public)
+            if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) {
+                boolean annotated = false;
+                AnnotationInstanceInfo[] annotations = clazz.annotations();
+                if (annotations.length > 0) {
+                    for (AnnotationInstanceInfo annotation : annotations) {
+                        if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
+                            widgets.add(clazz);
+                            annotated = true;
+                            break;
+                        } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
+                            layouts.add(clazz);
+                            annotated = true;
+                            break;
+                        }
+                    }
+                }
+                
+                if (annotated == false) {
+                    // lets check if this is inside android.widget
+                    PackageInfo pckg = clazz.containingPackage();
+                    String packageName = pckg.name();
+                    if ("android.widget".equals(packageName) ||
+                            "android.view".equals(packageName)) {
+                        // now we check what this class inherits either from android.view.ViewGroup
+                        // or android.view.View, or android.view.ViewGroup.LayoutParams
+                        int type = checkInheritance(clazz);
+                        switch (type) {
+                            case TYPE_WIDGET:
+                                widgets.add(clazz);
+                                break;
+                            case TYPE_LAYOUT:
+                                layouts.add(clazz);
+                                break;
+                            case TYPE_LAYOUT_PARAM:
+                                layoutParams.add(clazz);
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+
+        // now write the files, whether or not the list are empty.
+        // the SDK built requires those files to be present.
+
+        Collections.sort(activityActions);
+        writeValues(output + "/activity_actions.txt", activityActions);
+
+        Collections.sort(broadcastActions);
+        writeValues(output + "/broadcast_actions.txt", broadcastActions);
+
+        Collections.sort(serviceActions);
+        writeValues(output + "/service_actions.txt", serviceActions);
+
+        Collections.sort(categories);
+        writeValues(output + "/categories.txt", categories);
+        
+        // before writing the list of classes, we do some checks, to make sure the layout params
+        // are enclosed by a layout class (and not one that has been declared as a widget)
+        for (int i = 0 ; i < layoutParams.size();) {
+            ClassInfo layoutParamClass = layoutParams.get(i);
+            ClassInfo containingClass = layoutParamClass.containingClass();
+            if (containingClass == null || layouts.indexOf(containingClass) == -1) {
+                layoutParams.remove(i);
+            } else {
+                i++;
+            }
+        }
+        
+        writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
+    }
+    
+    /**
+     * Writes a list of values into a text files.
+     * @param pathname the absolute os path of the output file.
+     * @param values the list of values to write.
+     */
+    private static void writeValues(String pathname, ArrayList<String> values) {
+        FileWriter fw = null;
+        BufferedWriter bw = null;
+        try {
+            fw = new FileWriter(pathname, false);
+            bw = new BufferedWriter(fw);
+            
+            for (String value : values) {
+                bw.append(value).append('\n');
+            }
+        } catch (IOException e) {
+            // pass for now
+        } finally {
+            try {
+                if (bw != null) bw.close();
+            } catch (IOException e) {
+                // pass for now
+            }
+            try {
+                if (fw != null) fw.close();
+            } catch (IOException e) {
+                // pass for now
+            }
+        }
+    }
+
+    /**
+     * Writes the widget/layout/layout param classes into a text files.
+     * @param pathname the absolute os path of the output file.
+     * @param widgets the list of widget classes to write.
+     * @param layouts the list of layout classes to write.
+     * @param layoutParams the list of layout param classes to write.
+     */
+    private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
+            ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
+        FileWriter fw = null;
+        BufferedWriter bw = null;
+        try {
+            fw = new FileWriter(pathname, false);
+            bw = new BufferedWriter(fw);
+            
+            // write the 3 types of classes.
+            for (ClassInfo clazz : widgets) {
+                writeClass(bw, clazz, 'W');
+            }
+            for (ClassInfo clazz : layoutParams) {
+                writeClass(bw, clazz, 'P');
+            }
+            for (ClassInfo clazz : layouts) {
+                writeClass(bw, clazz, 'L');
+            }
+        } catch (IOException e) {
+            // pass for now
+        } finally {
+            try {
+                if (bw != null) bw.close();
+            } catch (IOException e) {
+                // pass for now
+            }
+            try {
+                if (fw != null) fw.close();
+            } catch (IOException e) {
+                // pass for now
+            }
+        }
+    }
+
+    /**
+     * Writes a class name and its super class names into a {@link BufferedWriter}.
+     * @param writer the BufferedWriter to write into
+     * @param clazz the class to write
+     * @param prefix the prefix to put at the beginning of the line.
+     * @throws IOException
+     */
+    private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
+            throws IOException {
+        writer.append(prefix).append(clazz.qualifiedName());
+        ClassInfo superClass = clazz;
+        while ((superClass = superClass.superclass()) != null) {
+            writer.append(' ').append(superClass.qualifiedName());
+        }
+        writer.append('\n');
+    }
+    
+    /**
+     * Checks the inheritance of {@link ClassInfo} objects. This method return
+     * <ul>
+     * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
+     * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
+     * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li>
+     * <li>{@link #TYPE_NONE}: in all other cases</li>
+     * </ul> 
+     * @param clazz the {@link ClassInfo} to check.
+     */
+    private static int checkInheritance(ClassInfo clazz) {
+        if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
+            return TYPE_LAYOUT;
+        } else if ("android.view.View".equals(clazz.qualifiedName())) {
+            return TYPE_WIDGET;
+        } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
+            return TYPE_LAYOUT_PARAM;
+        }
+        
+        ClassInfo parent = clazz.superclass();
+        if (parent != null) {
+            return checkInheritance(parent);
+        }
+        
+        return TYPE_NONE;
+    }
+}
diff --git a/tools/droiddoc/src/Errors.java b/tools/droiddoc/src/Errors.java
new file mode 100644
index 0000000..dfeac88
--- /dev/null
+++ b/tools/droiddoc/src/Errors.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class Errors
+{
+    public static boolean hadError = false;
+    private static boolean warningsAreErrors = false;
+    private static TreeSet<Message> allErrors = new TreeSet<Message>();
+
+    private static class Message implements Comparable {
+        SourcePositionInfo pos;
+        int level;
+        String msg;
+
+        Message(SourcePositionInfo p, int l, String m) {
+            pos = p;
+            level = l;
+            msg = m;
+        }
+
+        public int compareTo(Object o) {
+            Message that = (Message)o;
+            int r = this.pos.compareTo(that.pos);
+            if (r != 0) return r;
+            return this.msg.compareTo(that.msg);
+        }
+
+        public String toString() {
+            String whereText = this.pos == null ? "unknown: " : this.pos.toString() + ':';
+            return whereText + this.msg;
+        }
+    }
+
+    public static void error(Error error, SourcePositionInfo where, String text) {
+        if (error.level == HIDDEN) {
+            return;
+        }
+
+        int level = (!warningsAreErrors && error.level == WARNING) ? WARNING : ERROR;
+        String which = level == WARNING ? " warning " : " error ";
+        String message = which + error.code + ": " + text;
+
+        if (where == null) {
+            where = new SourcePositionInfo("unknown", 0, 0);
+        }
+
+        allErrors.add(new Message(where, level, message));
+
+        if (error.level == ERROR || (warningsAreErrors && error.level == WARNING)) {
+            hadError = true;
+        }
+    }
+
+    public static void printErrors() {
+        for (Message m: allErrors) {
+            if (m.level == WARNING) {
+                System.err.println(m.toString());
+            }
+        }
+        for (Message m: allErrors) {
+            if (m.level == ERROR) {
+                System.err.println(m.toString());
+            }
+        }
+    }
+
+    public static int HIDDEN = 0;
+    public static int WARNING = 1;
+    public static int ERROR = 2;
+
+    public static void setWarningsAreErrors(boolean val) {
+        warningsAreErrors = val;
+    }
+
+    public static class Error {
+        public int code;
+        public int level;
+
+        public Error(int code, int level)
+        {
+            this.code = code;
+            this.level = level;
+        }
+    }
+
+    public static Error UNRESOLVED_LINK = new Error(1, WARNING);
+    public static Error BAD_INCLUDE_TAG = new Error(2, WARNING);
+    public static Error UNKNOWN_TAG = new Error(3, WARNING);
+    public static Error UNKNOWN_PARAM_TAG_NAME = new Error(4, WARNING);
+    public static Error UNDOCUMENTED_PARAMETER = new Error(5, HIDDEN);
+    public static Error BAD_ATTR_TAG = new Error(6, ERROR);
+    public static Error BAD_INHERITDOC = new Error(7, HIDDEN);
+    public static Error HIDDEN_LINK = new Error(8, WARNING);
+    public static Error HIDDEN_CONSTRUCTOR = new Error(9, WARNING);
+    public static Error UNAVAILABLE_SYMBOL = new Error(10, ERROR);
+    public static Error HIDDEN_SUPERCLASS = new Error(11, WARNING);
+    public static Error DEPRECATED = new Error(12, HIDDEN);
+    public static Error DEPRECATION_MISMATCH = new Error(13, WARNING);
+    public static Error MISSING_COMMENT = new Error(14, WARNING);
+    public static Error IO_ERROR = new Error(15, HIDDEN);
+
+    public static Error[] ERRORS = {
+            UNRESOLVED_LINK,
+            BAD_INCLUDE_TAG,
+            UNKNOWN_TAG,
+            UNKNOWN_PARAM_TAG_NAME,
+            UNDOCUMENTED_PARAMETER,
+            BAD_ATTR_TAG,
+            BAD_INHERITDOC,
+            HIDDEN_LINK,
+            HIDDEN_CONSTRUCTOR,
+            UNAVAILABLE_SYMBOL,
+            HIDDEN_SUPERCLASS,
+            DEPRECATED,
+            IO_ERROR,
+        };
+
+    public static boolean setErrorLevel(int code, int level) {
+        for (Error e: ERRORS) {
+            if (e.code == code) {
+                e.level = level;
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/tools/droiddoc/src/FieldInfo.java b/tools/droiddoc/src/FieldInfo.java
new file mode 100644
index 0000000..536d798
--- /dev/null
+++ b/tools/droiddoc/src/FieldInfo.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+import java.util.Comparator;
+
+public class FieldInfo extends MemberInfo
+{
+    public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() {
+        public int compare(FieldInfo a, FieldInfo b) {
+            return a.name().compareTo(b.name());
+        }
+    };
+    
+    public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass,
+                        boolean isPublic, boolean isProtected,
+                        boolean isPackagePrivate, boolean isPrivate,
+                        boolean isFinal, boolean isStatic, boolean isTransient, boolean isVolatile,
+                        boolean isSynthetic, TypeInfo type, String rawCommentText,
+                        Object constantValue,
+                        SourcePositionInfo position,
+                        AnnotationInstanceInfo[] annotations)
+    {
+        super(rawCommentText, name, null, containingClass, realContainingClass,
+                isPublic, isProtected, isPackagePrivate, isPrivate,
+                isFinal, isStatic, isSynthetic, chooseKind(isFinal, isStatic), position,
+                annotations);
+        mIsTransient = isTransient;
+        mIsVolatile = isVolatile;
+        mType = type;
+        mConstantValue = constantValue;
+    }
+
+    public FieldInfo cloneForClass(ClassInfo newContainingClass) {
+        return new FieldInfo(name(), newContainingClass, realContainingClass(),
+                isPublic(), isProtected(), isPackagePrivate(),
+                isPrivate(), isFinal(), isStatic(), isTransient(), isVolatile(),
+                isSynthetic(), mType, getRawCommentText(), mConstantValue, position(),
+                annotations());
+    }
+
+    static String chooseKind(boolean isFinal, boolean isStatic)
+    {
+        if (isStatic && isFinal) {
+            return "constant";
+        } else {
+            return "field";
+        }
+    }
+
+    public TypeInfo type()
+    {
+        return mType;
+    }
+
+    public boolean isConstant()
+    {
+        return isStatic() && isFinal();
+    }
+
+    public TagInfo[] firstSentenceTags()
+    {
+        return comment().briefTags();
+    }
+
+    public TagInfo[] inlineTags()
+    {
+        return comment().tags();
+    }
+
+    public Object constantValue()
+    {
+        return mConstantValue;
+    }
+
+    public String constantLiteralValue()
+    {
+        return constantLiteralValue(mConstantValue);
+    }
+    
+    public boolean isDeprecated() {
+        boolean deprecated = false;
+        if (!mDeprecatedKnown) {
+            boolean commentDeprecated = (comment().deprecatedTags().length > 0);
+            boolean annotationDeprecated = false;
+            for (AnnotationInstanceInfo annotation : annotations()) {
+                if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
+                    annotationDeprecated = true;
+                    break;
+                }
+            }
+
+            if (commentDeprecated != annotationDeprecated) {
+                Errors.error(Errors.DEPRECATION_MISMATCH, position(),
+                        "Field " + mContainingClass.qualifiedName() + "." + name()
+                        + ": @Deprecated annotation and @deprecated comment do not match");
+            }
+
+            mIsDeprecated = commentDeprecated | annotationDeprecated;
+            mDeprecatedKnown = true;
+        }
+        return mIsDeprecated;
+    }
+
+    public static String constantLiteralValue(Object val)
+    {
+        String str = null;
+        if (val != null) {
+            if (val instanceof Boolean
+                    || val instanceof Byte
+                    || val instanceof Short
+                    || val instanceof Integer) 
+            {
+                str = val.toString();
+            }
+            //catch all special values
+            else if (val instanceof Double){
+                Double dbl = (Double) val;
+                    if (dbl.toString().equals("Infinity")){
+                        str = "(1.0 / 0.0)";
+                    } else if (dbl.toString().equals("-Infinity")) {
+                        str = "(-1.0 / 0.0)";
+                    } else if (dbl.isNaN()) {
+                        str = "(0.0 / 0.0)";
+                    } else {
+                        str = dbl.toString();
+                    }
+            }
+            else if (val instanceof Long) {
+                str = val.toString() + "L";
+            }
+            else if (val instanceof Float) {
+                Float fl = (Float) val;
+                if (fl.toString().equals("Infinity")) {
+                    str = "(1.0f / 0.0f)";
+                } else if (fl.toString().equals("-Infinity")) {
+                    str = "(-1.0f / 0.0f)";
+                } else if (fl.isNaN()) {
+                    str = "(0.0f / 0.0f)";
+                } else {
+                    str = val.toString() + "f";
+                }
+            }
+            else if (val instanceof Character) {
+                str = String.format("\'\\u%04x\'", val);
+            }
+            else if (val instanceof String) {
+                str = "\"" + javaEscapeString((String)val) + "\"";
+            }
+            else {
+                str = "<<<<" +val.toString() + ">>>>";
+            }
+        }
+        if (str == null) {
+            str = "null";
+        }
+        return str;
+    }
+
+    public static String javaEscapeString(String str) {
+        String result = "";
+        final int N = str.length();
+        for (int i=0; i<N; i++) {
+            char c = str.charAt(i);
+            if (c == '\\') {
+                result += "\\\\";
+            }
+            else if (c == '\t') {
+                result += "\\t";
+            }
+            else if (c == '\b') {
+                result += "\\b";
+            }
+            else if (c == '\r') {
+                result += "\\r";
+            }
+            else if (c == '\n') {
+                result += "\\n";
+            }
+            else if (c == '\f') {
+                result += "\\f";
+            }
+            else if (c == '\'') {
+                result += "\\'";
+            }
+            else if (c == '\"') {
+                result += "\\\"";
+            }
+            else if (c >= ' ' && c <= '~') {
+                result += c;
+            }
+            else {
+                result += String.format("\\u%04x", new Integer((int)c));
+            }
+        }
+        return result;
+    }
+
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".kind", kind());
+        type().makeHDF(data, base + ".type");
+        data.setValue(base + ".name", name());
+        data.setValue(base + ".href", htmlPage());
+        data.setValue(base + ".anchor", anchor());
+        TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
+        TagInfo.makeHDF(data, base + ".descr", inlineTags());
+        TagInfo.makeHDF(data, base + ".deprecated", comment().deprecatedTags());
+        TagInfo.makeHDF(data, base + ".seeAlso", comment().seeTags());
+        data.setValue(base + ".final", isFinal() ? "final" : "");
+        data.setValue(base + ".static", isStatic() ? "static" : "");
+        if (isPublic()) {
+            data.setValue(base + ".scope", "public");
+        }
+        else if (isProtected()) {
+            data.setValue(base + ".scope", "protected");
+        }
+        else if (isPackagePrivate()) {
+            data.setValue(base + ".scope", "");
+        }
+        else if (isPrivate()) {
+            data.setValue(base + ".scope", "private");
+        }
+        Object val = mConstantValue;
+        if (val != null) {
+            String dec = null;
+            String hex = null;
+            String str = null;
+
+            if (val instanceof Boolean) {
+                str = ((Boolean)val).toString();
+            }
+            else if (val instanceof Byte) {
+                dec = String.format("%d", val);
+                hex = String.format("0x%02x", val);
+            }
+            else if (val instanceof Character) {
+                dec = String.format("\'%c\'", val);
+                hex = String.format("0x%04x", val);
+            }
+            else if (val instanceof Double) {
+                str = ((Double)val).toString();
+            }
+            else if (val instanceof Float) {
+                str = ((Float)val).toString();
+            }
+            else if (val instanceof Integer) {
+                dec = String.format("%d", val);
+                hex = String.format("0x%08x", val);
+            }
+            else if (val instanceof Long) {
+                dec = String.format("%d", val);
+                hex = String.format("0x%016x", val);
+            }
+            else if (val instanceof Short) {
+                dec = String.format("%d", val);
+                hex = String.format("0x%04x", val);
+            }
+            else if (val instanceof String) {
+                str = "\"" + ((String)val) + "\"";
+            }
+            else {
+                str = "";
+            }
+
+            if (dec != null && hex != null) {
+                data.setValue(base + ".constantValue.dec", DroidDoc.escape(dec));
+                data.setValue(base + ".constantValue.hex", DroidDoc.escape(hex));
+            }
+            else {
+                data.setValue(base + ".constantValue.str", DroidDoc.escape(str));
+                data.setValue(base + ".constantValue.isString", "1");
+            }
+        }
+    }
+
+    public boolean isExecutable()
+    {
+        return false;
+    }
+
+    public boolean isTransient()
+    {
+        return mIsTransient;
+    }
+
+    public boolean isVolatile()
+    {
+        return mIsVolatile;
+    }
+
+    boolean mIsTransient;
+    boolean mIsVolatile;
+    boolean mDeprecatedKnown;
+    boolean mIsDeprecated;
+    TypeInfo mType;
+    Object mConstantValue;
+}
+
diff --git a/tools/droiddoc/src/Hierarchy.java b/tools/droiddoc/src/Hierarchy.java
new file mode 100755
index 0000000..ac5e1dc
--- /dev/null
+++ b/tools/droiddoc/src/Hierarchy.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.HashMap;
+import java.util.TreeSet;
+import java.util.Set;
+import org.clearsilver.HDF;
+
+public class Hierarchy
+{
+    public static void makeHierarchy(HDF hdf, ClassInfo[] classes)
+    {
+        HashMap<String,TreeSet<String>> nodes
+                                    = new HashMap<String,TreeSet<String>>();
+
+        for (ClassInfo cl: classes) {
+            String name = cl.qualifiedName();
+
+            TreeSet<String> me = nodes.get(name);
+            if (me == null) {
+                me = new TreeSet<String>();
+                nodes.put(name, me);
+            }
+
+            ClassInfo superclass = cl.superclass();
+            String sname = superclass != null
+                                    ? superclass.qualifiedName() : null;
+            if (sname != null) {
+                TreeSet<String> s = nodes.get(sname);
+                if (s == null) {
+                    s = new TreeSet<String>();
+                    nodes.put(sname, s);
+                }
+                s.add(name);
+            }
+        }
+
+        /*
+        Set<String> keys = nodes.keySet();
+        for (String n: keys) {
+            System.out.println("class: " + n);
+
+            TreeSet<String> values = nodes.get(n);
+            for (String v: values) {
+                System.out.println("       - " + v);
+            }
+        }
+        */
+
+        int depth = depth(nodes, "java.lang.Object");
+
+        hdf.setValue("classes.0", "");
+        hdf.setValue("colspan", "" + depth);
+
+        recurse(nodes, "java.lang.Object", hdf.getObj("classes.0"),depth,depth);
+
+        if (false) {
+            Set<String> keys = nodes.keySet();
+            if (keys.size() > 0) {
+                System.err.println("The following classes are hidden but"
+                        + " are superclasses of not-hidden classes");
+                for (String n: keys) {
+                    System.err.println("  " + n);
+                }
+            }
+        }
+    }
+
+    private static int depth(HashMap<String,TreeSet<String>> nodes,
+                                String name)
+    {
+        int d = 0;
+        TreeSet<String> derived = nodes.get(name);
+        if (derived != null && derived.size() > 0) {
+            for (String s: derived) {
+                int n = depth(nodes, s);
+                if (n > d) {
+                    d = n;
+                }
+            }
+        }
+        return d + 1;
+    }
+
+    private static boolean exists(ClassInfo cl)
+    {
+        return cl != null && !cl.isHidden() && cl.isIncluded();
+    }
+
+    private static void recurse(HashMap<String,TreeSet<String>> nodes,
+                                String name, HDF hdf, 
+                                int totalDepth, int remainingDepth)
+    {
+        int i;
+
+        hdf.setValue("indent", "" + (totalDepth-remainingDepth-1));
+        hdf.setValue("colspan", "" + remainingDepth);
+
+        ClassInfo cl = Converter.obtainClass(name);
+
+        hdf.setValue("class.label", cl.name());
+        hdf.setValue("class.qualified", cl.qualifiedName());
+        if (cl.checkLevel()) {
+            hdf.setValue("class.link", cl.htmlPage());
+        }
+
+        if (exists(cl)) {
+            hdf.setValue("exists", "1");
+        }
+
+        i = 0;
+        for (ClassInfo iface: cl.interfaces()) {
+            hdf.setValue("interfaces." + i + ".class.label", iface.name());
+            hdf.setValue("interfaces." + i + ".class.qualified", iface.qualifiedName());
+            if (iface.checkLevel()) {
+                hdf.setValue("interfaces." + i + ".class.link", iface.htmlPage());
+            }
+            if (exists(cl)) {
+                hdf.setValue("interfaces." + i + ".exists", "1");
+            }
+            i++;
+        }
+
+        TreeSet<String> derived = nodes.get(name);
+        if (derived != null && derived.size() > 0) {
+            hdf.setValue("derived", "");
+            HDF children = hdf.getObj("derived");
+            i = 0;
+            remainingDepth--;
+            for (String s: derived) {
+                String index = "" + i;
+                children.setValue(index, "");
+                recurse(nodes, s, children.getObj(index), totalDepth,
+                        remainingDepth);
+                i++;
+            }
+        }
+
+        nodes.remove(name);
+    }
+}
+
diff --git a/tools/droiddoc/src/InheritedTags.java b/tools/droiddoc/src/InheritedTags.java
new file mode 100644
index 0000000..242170c
--- /dev/null
+++ b/tools/droiddoc/src/InheritedTags.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public interface InheritedTags
+{
+    TagInfo[] tags();
+    InheritedTags inherited();
+}
+
diff --git a/tools/droiddoc/src/KeywordEntry.java b/tools/droiddoc/src/KeywordEntry.java
new file mode 100644
index 0000000..7e5e357
--- /dev/null
+++ b/tools/droiddoc/src/KeywordEntry.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+class KeywordEntry implements Comparable
+{
+    KeywordEntry(String label, String href, String comment)
+    {
+        this.label = label;
+        this.href = href;
+        this.comment = comment;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".label", this.label);
+        data.setValue(base + ".href", this.href);
+        data.setValue(base + ".comment", this.comment);
+    }
+
+    public char firstChar()
+    {
+        return Character.toUpperCase(this.label.charAt(0));
+    }
+
+    public int compareTo(Object that)
+    {
+        return this.label.compareToIgnoreCase(((KeywordEntry)that).label);
+    }
+
+    private String label;
+    private String href;
+    private String comment;
+}
+
+
diff --git a/tools/droiddoc/src/LinkReference.java b/tools/droiddoc/src/LinkReference.java
new file mode 100644
index 0000000..bbcd4db
--- /dev/null
+++ b/tools/droiddoc/src/LinkReference.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+
+/**
+ * Class that represents what you see in an link or see tag.  This is
+ * factored out of SeeTagInfo so it can be used elsewhere (like AttrTagInfo).
+ */
+public class LinkReference {
+
+    /** The original text. */
+    public String text;
+    
+    /** The kind of this tag, if we have a new suggestion after parsing. */
+    public String kind;
+
+    /** The user visible text. */
+    public String label;
+
+    /** The link. */
+    public String href;
+
+    /** The {@link PackageInfo} if any. */
+    public PackageInfo packageInfo;
+
+    /** The {@link ClassInfo} if any. */
+    public ClassInfo classInfo;
+
+    /** The {@link MemberInfo} if any. */
+    public MemberInfo memberInfo;
+
+    /** The name of the referenced member PackageInfo} if any. */
+    public String referencedMemberName;
+
+    /** Set to true if everything is a-ok */
+    public boolean good;
+
+    /**
+     * regex pattern to use when matching explicit "<a href" reference text
+     */
+    private static final Pattern HREF_PATTERN
+            = Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$",
+                              Pattern.CASE_INSENSITIVE);
+
+    /**
+     * Parse and resolve a link string.
+     *
+     * @param text the original text
+     * @param base the class or whatever that this link is on
+     * @param pos the original position in the source document
+     * @return a new link reference.  It always returns something.  If there was an
+     *         error, it logs it and fills in href and label with error text.
+     */
+    public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
+                                        boolean printOnErrors) {
+        LinkReference result = new LinkReference();
+        result.text = text;
+
+        int index;
+        int len = text.length();
+        int pairs = 0;
+        int pound = -1;
+        // split the string
+        done: {
+            for (index=0; index<len; index++) {
+                char c = text.charAt(index);
+                switch (c)
+                {
+                    case '(':
+                        pairs++;
+                        break;
+                    case '[':
+                        pairs++;
+                        break;
+                    case ')':
+                        pairs--;
+                        break;
+                    case ']':
+                        pairs--;
+                        break;
+                    case ' ':
+                    case '\t':
+                    case '\r':
+                    case '\n':
+                        if (pairs == 0) {
+                            break done;
+                        }
+                        break;
+                    case '#':
+                        if (pound < 0) {
+                            pound = index;
+                        }
+                        break;
+                }
+            }
+        }
+        if (index == len && pairs != 0) {
+            Errors.error(Errors.UNRESOLVED_LINK, pos,
+                        "unable to parse link/see tag: " + text.trim());
+            return result;
+        }
+
+        int linkend = index;
+
+        for (; index<len; index++) {
+            char c = text.charAt(index);
+            if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
+                break;
+            }
+        }
+
+        result.label = text.substring(index);
+
+        String ref;
+        String mem;
+        if (pound == 0) {
+            ref = null;
+            mem = text.substring(1, linkend);
+        }
+        else if (pound > 0) {
+            ref = text.substring(0, pound);
+            mem = text.substring(pound+1, linkend);
+        }
+        else {
+            ref = text.substring(0, linkend);
+            mem = null;
+        }
+
+        // parse parameters, if any
+        String[] params = null;
+        String[] paramDimensions = null;
+        if (mem != null) {
+            index = mem.indexOf('(');
+            if (index > 0) {
+                ArrayList<String> paramList = new ArrayList<String>();
+                ArrayList<String> paramDimensionList = new ArrayList<String>();
+                len = mem.length();
+                int start = index+1;
+                final int START = 0;
+                final int TYPE = 1;
+                final int NAME = 2;
+                int dimension = 0;
+                int arraypair = 0;
+                int state = START;
+                int typestart = 0;
+                int typeend = -1;
+                for (int i=start; i<len; i++) {
+                    char c = mem.charAt(i);
+                    switch (state)
+                    {
+                        case START:
+                            if (c!=' ' && c!='\t' && c!='\r' && c!='\n') {
+                                state = TYPE;
+                                typestart = i;
+                            }
+                            break;
+                        case TYPE:
+                            if (c == '[') {
+                                if (typeend < 0) {
+                                    typeend = i;
+                                }
+                                dimension++;
+                                arraypair++;
+                            }
+                            else if (c == ']') {
+                                arraypair--;
+                            }
+                            else if (c==' ' || c=='\t' || c=='\r' || c=='\n') {
+                                if (typeend < 0) {
+                                    typeend = i;
+                                }
+                            }
+                            else {
+                                if (typeend >= 0 || c == ')' || c == ',') {
+                                    if (typeend < 0) {
+                                        typeend = i;
+                                    }
+                                    String s = mem.substring(typestart, typeend);
+                                    paramList.add(s);
+                                    s = "";
+                                    for (int j=0; j<dimension; j++) {
+                                        s += "[]";
+                                    }
+                                    paramDimensionList.add(s);
+                                    state = START;
+                                    typeend = -1;
+                                    dimension = 0;
+                                    if (c == ',' || c == ')') {
+                                        state = START;
+                                    } else {
+                                        state = NAME;
+                                    }
+                                }
+                            }
+                            break;
+                        case NAME:
+                            if (c == ',' || c == ')') {
+                                state = START;
+                            }
+                            break;
+                    }
+
+                }
+                params = paramList.toArray(new String[paramList.size()]);
+                paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
+                mem = mem.substring(0, index);
+            }
+        }
+
+        ClassInfo cl = null;
+        if (base instanceof ClassInfo) {
+            cl = (ClassInfo)base;
+        }
+
+        if (ref == null) {
+            // no class or package was provided, assume it's this class
+            if (cl != null) {
+                result.classInfo = cl;
+            }
+        } else {
+            // they provided something, maybe it's a class or a package
+            if (cl != null) {
+                result.classInfo = cl.extendedFindClass(ref);
+                if (result.classInfo == null) {
+                    result.classInfo = cl.findClass(ref);
+                }
+                if (result.classInfo == null) {
+                    result.classInfo = cl.findInnerClass(ref);
+                }
+            }
+            if (result.classInfo == null) {
+                result.classInfo = Converter.obtainClass(ref);
+            }
+            if (result.classInfo == null) {
+                result.packageInfo = Converter.obtainPackage(ref);
+            }
+        }
+
+        if (result.classInfo != null && mem != null) {
+            // it's either a field or a method, prefer a field
+            if (params == null) {
+                FieldInfo field = result.classInfo.findField(mem);
+                // findField looks in containing classes, so it might actually
+                // be somewhere else; link to where it really is, not what they
+                // typed.
+                if (field != null) {
+                    result.classInfo = field.containingClass();
+                    result.memberInfo = field;
+                }
+            }
+            if (result.memberInfo == null) {
+                MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions);
+                if (method != null) {
+                    result.classInfo = method.containingClass();
+                    result.memberInfo = method;
+                }
+            }
+        }
+
+        result.referencedMemberName = mem;
+        if (params != null) {
+            result.referencedMemberName = result.referencedMemberName + '(';
+            len = params.length;
+            if (len > 0) {
+                len--;
+                for (int i=0; i<len; i++) {
+                    result.referencedMemberName = result.referencedMemberName + params[i]
+                            + paramDimensions[i] + ", ";
+                }
+                result.referencedMemberName = result.referencedMemberName + params[len]
+                        + paramDimensions[len];
+            }
+            result.referencedMemberName = result.referencedMemberName + ")";
+        }
+
+        // debugging spew
+        if (false) {
+            result.label = result.label + "/" + ref + "/" + mem + '/';
+            if (params != null) {
+                for (int i=0; i<params.length; i++) {
+                    result.label += params[i] + "|";
+                }
+            }
+
+            FieldInfo f = (result.memberInfo instanceof FieldInfo)
+                        ? (FieldInfo)result.memberInfo
+                        : null;
+            MethodInfo m = (result.memberInfo instanceof MethodInfo)
+                        ? (MethodInfo)result.memberInfo
+                        : null;
+            result.label = result.label
+                        + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"")
+                        + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"")
+                        + "/field=" + (f!=null?f.name():"")
+                        + "/method=" + (m!=null?m.name():"");
+            
+        }
+
+        MethodInfo method = null;
+        boolean skipHref = false;
+
+        if (result.memberInfo != null && result.memberInfo.isExecutable()) {
+           method = (MethodInfo)result.memberInfo;
+        }
+
+        if (text.startsWith("\"")) {
+            // literal quoted reference (e.g., a book title)
+            result.label = text.substring(1);
+            skipHref = true;
+            if (!result.label.endsWith("\"")) {
+                Errors.error(Errors.UNRESOLVED_LINK, pos,
+                        "unbalanced quoted link/see tag: " + text.trim());
+                result.makeError();
+                return result;
+            }
+            result.label = result.label.substring(0, result.label.length() - 1);
+            result.kind = "@seeJustLabel";
+        }
+        else if (text.startsWith("<")) {
+            // explicit "<a href" form
+            Matcher matcher = HREF_PATTERN.matcher(text);
+            if (! matcher.matches()) {
+                Errors.error(Errors.UNRESOLVED_LINK, pos,
+                        "invalid <a> link/see tag: " + text.trim());
+                result.makeError();
+                return result;
+            }
+            result.href = matcher.group(1);
+            result.label = matcher.group(2);
+            result.kind = "@seeHref";
+        }
+        else if (result.packageInfo != null) {
+            result.href = result.packageInfo.htmlPage();
+            if (result.label.length() == 0) {
+                result.href = result.packageInfo.htmlPage();
+                result.label = result.packageInfo.name();
+            }
+        }
+        else if (result.classInfo != null && result.referencedMemberName == null) {
+            // class reference
+            if (result.label.length() == 0) {
+                result.label = result.classInfo.name();
+            }
+            result.href = result.classInfo.htmlPage();
+        }
+        else if (result.memberInfo != null) {
+            // member reference
+            ClassInfo containing = result.memberInfo.containingClass();
+            if (result.memberInfo.isExecutable()) {
+                if (result.referencedMemberName.indexOf('(') < 0) {
+                    result.referencedMemberName += method.flatSignature();
+                }
+            } 
+            if (result.label.length() == 0) {
+                result.label = result.referencedMemberName;
+            }
+            result.href = containing.htmlPage() + '#' + result.memberInfo.anchor();
+        }
+
+        if (result.href == null && !skipHref) {
+            if (printOnErrors && (base == null || base.checkLevel())) {
+                Errors.error(Errors.UNRESOLVED_LINK, pos,
+                        "Unresolved link/see tag \"" + text.trim()
+                        + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
+            }
+            result.makeError();
+        }
+        else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
+            if (printOnErrors && (base == null || base.checkLevel())) {
+                Errors.error(Errors.HIDDEN_LINK, pos,
+                        "Link to hidden member: " + text.trim());
+                result.href = null;
+            }
+            result.kind = "@seeJustLabel";
+        }
+        else if (result.classInfo != null && !result.classInfo.checkLevel()) {
+            if (printOnErrors && (base == null || base.checkLevel())) {
+                Errors.error(Errors.HIDDEN_LINK, pos,
+                        "Link to hidden class: " + text.trim() + " label=" + result.label);
+                result.href = null;
+            }
+            result.kind = "@seeJustLabel";
+        }
+        else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
+            if (printOnErrors && (base == null || base.checkLevel())) {
+                Errors.error(Errors.HIDDEN_LINK, pos,
+                        "Link to hidden package: " + text.trim());
+                result.href = null;
+            }
+            result.kind = "@seeJustLabel";
+        }
+
+        result.good = true;
+
+        return result;
+    }
+
+    public boolean checkLevel() {
+        if (memberInfo != null) {
+            return memberInfo.checkLevel();
+        }
+        if (classInfo != null) {
+            return classInfo.checkLevel();
+        }
+        if (packageInfo != null) {
+            return packageInfo.checkLevel();
+        }
+        return false;
+    }
+
+    /** turn this LinkReference into one with an error message */
+    private void makeError() {
+        //this.href = "ERROR(" + this.text.trim() + ")";
+        this.href = null;
+        if (this.label == null) {
+            this.label = "";
+        }
+        this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
+    }
+
+    /** private. **/
+    private LinkReference() {
+    }
+}
diff --git a/tools/droiddoc/src/LiteralTagInfo.java b/tools/droiddoc/src/LiteralTagInfo.java
new file mode 100644
index 0000000..b39490d
--- /dev/null
+++ b/tools/droiddoc/src/LiteralTagInfo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class LiteralTagInfo extends TagInfo
+{
+    private static String encode(String t)
+    {
+        t = t.replace("&", "&amp;");
+        t = t.replace("<", "&lt;");
+        t = t.replace(">", "&gt;");
+        return t;
+    }
+
+    public LiteralTagInfo(String n, String k, String t, SourcePositionInfo sp)
+    {
+        super("Text", "Text", encode(t), sp);
+    }
+}
diff --git a/tools/droiddoc/src/MemberInfo.java b/tools/droiddoc/src/MemberInfo.java
new file mode 100644
index 0000000..2a2572a
--- /dev/null
+++ b/tools/droiddoc/src/MemberInfo.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public abstract class MemberInfo extends DocInfo implements Comparable, Scoped
+{
+    public MemberInfo(String rawCommentText, String name, String signature,
+                        ClassInfo containingClass, ClassInfo realContainingClass,
+                        boolean isPublic, boolean isProtected,
+                        boolean isPackagePrivate, boolean isPrivate,
+                        boolean isFinal, boolean isStatic, boolean isSynthetic,
+                        String kind,
+                        SourcePositionInfo position,
+                        AnnotationInstanceInfo[] annotations)
+    {
+        super(rawCommentText, position);
+        mName = name;
+        mSignature = signature;
+        mContainingClass = containingClass;
+        mRealContainingClass = realContainingClass;
+        mIsPublic = isPublic;
+        mIsProtected = isProtected;
+        mIsPackagePrivate = isPackagePrivate;
+        mIsPrivate = isPrivate;
+        mIsFinal = isFinal;
+        mIsStatic = isStatic;
+        mIsSynthetic = isSynthetic;
+        mKind = kind;
+        mAnnotations = annotations;
+    }
+
+    public abstract boolean isExecutable();
+
+    public String anchor()
+    {
+        if (mSignature != null) {
+            return mName + mSignature;
+        } else {
+            return mName;
+        }
+    }
+
+    public String htmlPage() {
+        return mContainingClass.htmlPage() + "#" + anchor();
+    }
+
+    public int compareTo(Object that) {
+        return this.htmlPage().compareTo(((MemberInfo)that).htmlPage());
+    }
+
+    public String name()
+    {
+        return mName;
+    }
+
+    public String signature()
+    {
+        return mSignature;
+    }
+
+    public ClassInfo realContainingClass()
+    {
+        return mRealContainingClass;
+    }
+
+    public ClassInfo containingClass()
+    {
+        return mContainingClass;
+    }
+
+    public boolean isPublic()
+    {
+        return mIsPublic;
+    }
+
+    public boolean isProtected()
+    {
+        return mIsProtected;
+    }
+
+    public boolean isPackagePrivate()
+    {
+        return mIsPackagePrivate;
+    }
+
+    public boolean isPrivate()
+    {
+        return mIsPrivate;
+    }
+
+    public boolean isStatic()
+    {
+        return mIsStatic;
+    }
+
+    public boolean isFinal()
+    {
+        return mIsFinal;
+    }
+
+    public boolean isSynthetic()
+    {
+        return mIsSynthetic;
+    }
+
+    public ContainerInfo parent()
+    {
+        return mContainingClass;
+    }
+
+    public boolean checkLevel()
+    {
+        return DroidDoc.checkLevel(mIsPublic, mIsProtected,
+                mIsPackagePrivate, mIsPrivate, isHidden());
+    }
+
+    public String kind()
+    {
+        return mKind;
+    }
+    
+    public AnnotationInstanceInfo[] annotations()
+    {
+        return mAnnotations;
+    }
+
+    ClassInfo mContainingClass;
+    ClassInfo mRealContainingClass;
+    String mName;
+    String mSignature;
+    boolean mIsPublic;
+    boolean mIsProtected;
+    boolean mIsPackagePrivate;
+    boolean mIsPrivate;
+    boolean mIsFinal;
+    boolean mIsStatic;
+    boolean mIsSynthetic;
+    String mKind;
+    private AnnotationInstanceInfo[] mAnnotations;
+
+}
+
diff --git a/tools/droiddoc/src/MethodInfo.java b/tools/droiddoc/src/MethodInfo.java
new file mode 100644
index 0000000..ca30665
--- /dev/null
+++ b/tools/droiddoc/src/MethodInfo.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public class MethodInfo extends MemberInfo
+{
+    public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
+        public int compare(MethodInfo a, MethodInfo b) {
+            return a.name().compareTo(b.name());
+        }
+    };
+    
+    private class InlineTags implements InheritedTags
+    { 
+        public TagInfo[] tags()
+        {
+            return comment().tags();
+        }
+        public InheritedTags inherited()
+        {
+            MethodInfo m = findOverriddenMethod(name(), signature());
+            if (m != null) {
+                return m.inlineTags();
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
+    {
+        for (ClassInfo i: ifaces) {
+            queue.add(i);
+        }
+        for (ClassInfo i: ifaces) {
+            addInterfaces(i.interfaces(), queue);
+        }
+    }
+
+    // first looks for a superclass, and then does a breadth first search to
+    // find the least far away match
+    public MethodInfo findOverriddenMethod(String name, String signature)
+    {
+        if (mReturnType == null) {
+            // ctor
+            return null;
+        }
+        if (mOverriddenMethod != null) {
+            return mOverriddenMethod;
+        }
+
+        ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
+        addInterfaces(containingClass().interfaces(), queue);
+        for (ClassInfo iface: queue) {
+            for (MethodInfo me: iface.methods()) {
+                if (me.name().equals(name)
+                        && me.signature().equals(signature)
+                        && me.inlineTags().tags() != null
+                        && me.inlineTags().tags().length > 0) {
+                    return me;
+                }
+            }
+        }
+        return null;
+    }
+    
+    private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
+    {
+        for (ClassInfo i: ifaces) {
+            queue.add(i);
+            if (i.realSuperclass() != null &&  i.realSuperclass().isAbstract()) {
+                queue.add(i.superclass());
+            }
+        }
+        for (ClassInfo i: ifaces) {
+            addInterfaces(i.realInterfaces(), queue);
+        }
+    }
+    
+    public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
+        if (mReturnType == null) {
+        // ctor
+        return null;
+        }
+        if (mOverriddenMethod != null) {
+            return mOverriddenMethod;
+        }
+
+        ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
+        if (containingClass().realSuperclass() != null && 
+            containingClass().realSuperclass().isAbstract()) {
+            queue.add(containingClass());
+        }
+        addInterfaces(containingClass().realInterfaces(), queue);
+        for (ClassInfo iface: queue) {
+            for (MethodInfo me: iface.methods()) {
+                if (me.name().equals(name)
+                    && me.signature().equals(signature)
+                    && me.inlineTags().tags() != null
+                    && me.inlineTags().tags().length > 0
+                    && notStrippable.contains(me.containingClass())) {
+                return me;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public MethodInfo findSuperclassImplementation(HashSet notStrippable) {
+        if (mReturnType == null) {
+            // ctor
+            return null;
+        }
+        if (mOverriddenMethod != null) {
+            // Even if we're told outright that this was the overridden method, we want to
+            // be conservative and ignore mismatches of parameter types -- they arise from
+            // extending generic specializations, and we want to consider the derived-class
+            // method to be a non-override.
+            if (this.signature().equals(mOverriddenMethod.signature())) {
+                return mOverriddenMethod;
+            }
+        }
+
+        ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
+        if (containingClass().realSuperclass() != null && 
+                containingClass().realSuperclass().isAbstract()) {
+            queue.add(containingClass());
+        }
+        addInterfaces(containingClass().realInterfaces(), queue);
+        for (ClassInfo iface: queue) {
+            for (MethodInfo me: iface.methods()) {
+                if (me.name().equals(this.name())
+                        && me.signature().equals(this.signature())
+                        && notStrippable.contains(me.containingClass())) {
+                    return me;
+                }
+            }
+        }
+        return null;
+    }
+    
+    public ClassInfo findRealOverriddenClass(String name, String signature) {
+        if (mReturnType == null) {
+        // ctor
+        return null;
+        }
+        if (mOverriddenMethod != null) {
+            return mOverriddenMethod.mRealContainingClass;
+        }
+
+        ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
+        if (containingClass().realSuperclass() != null && 
+            containingClass().realSuperclass().isAbstract()) {
+            queue.add(containingClass());
+        }
+        addInterfaces(containingClass().realInterfaces(), queue);
+        for (ClassInfo iface: queue) {
+            for (MethodInfo me: iface.methods()) {
+                if (me.name().equals(name)
+                    && me.signature().equals(signature)
+                    && me.inlineTags().tags() != null
+                    && me.inlineTags().tags().length > 0) {
+                return iface;
+                }
+            }
+        }
+        return null;
+    }
+
+    private class FirstSentenceTags implements InheritedTags
+    {
+        public TagInfo[] tags()
+        {
+            return comment().briefTags();
+        }
+        public InheritedTags inherited()
+        {
+            MethodInfo m = findOverriddenMethod(name(), signature());
+            if (m != null) {
+                return m.firstSentenceTags();
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    private class ReturnTags implements InheritedTags {
+        public TagInfo[] tags() {
+            return comment().returnTags();
+        }
+        public InheritedTags inherited() {
+            MethodInfo m = findOverriddenMethod(name(), signature());
+            if (m != null) {
+                return m.returnTags();
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    public boolean isDeprecated() {
+        boolean deprecated = false;
+        if (!mDeprecatedKnown) {
+            boolean commentDeprecated = (comment().deprecatedTags().length > 0);
+            boolean annotationDeprecated = false;
+            for (AnnotationInstanceInfo annotation : annotations()) {
+                if (annotation.type().qualifiedName().equals("java.lang.Deprecated")) {
+                    annotationDeprecated = true;
+                    break;
+                }
+            }
+
+            if (commentDeprecated != annotationDeprecated) {
+                Errors.error(Errors.DEPRECATION_MISMATCH, position(),
+                        "Method " + mContainingClass.qualifiedName() + "." + name()
+                        + ": @Deprecated annotation and @deprecated doc tag do not match");
+            }
+
+            mIsDeprecated = commentDeprecated | annotationDeprecated;
+            mDeprecatedKnown = true;
+        }
+        return mIsDeprecated;
+    }
+    
+    public TypeInfo[] getTypeParameters(){
+        return mTypeParameters;
+    }
+
+    public MethodInfo cloneForClass(ClassInfo newContainingClass) {
+        MethodInfo result =  new MethodInfo(getRawCommentText(), mTypeParameters,
+                name(), signature(), newContainingClass, realContainingClass(),
+                isPublic(), isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(),
+                isSynthetic(), mIsAbstract, mIsSynchronized, mIsNative, mIsAnnotationElement,
+                kind(), mFlatSignature, mOverriddenMethod,
+                mReturnType, mParameters, mThrownExceptions, position(), annotations());
+        result.init(mDefaultAnnotationElementValue);
+        return result;
+    }
+
+    public MethodInfo(String rawCommentText, TypeInfo[] typeParameters, String name,
+                        String signature, ClassInfo containingClass, ClassInfo realContainingClass,
+                        boolean isPublic, boolean isProtected,
+                        boolean isPackagePrivate, boolean isPrivate,
+                        boolean isFinal, boolean isStatic, boolean isSynthetic,
+                        boolean isAbstract, boolean isSynchronized, boolean isNative,
+                        boolean isAnnotationElement, String kind,
+                        String flatSignature, MethodInfo overriddenMethod,
+                        TypeInfo returnType, ParameterInfo[] parameters,
+                        ClassInfo[] thrownExceptions, SourcePositionInfo position,
+                        AnnotationInstanceInfo[] annotations)
+    {
+        // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
+        // the Java5-emitted base API description.
+        super(rawCommentText, name, signature, containingClass, realContainingClass,
+                isPublic, isProtected, isPackagePrivate, isPrivate,
+                ((name.equals("values") && containingClass.isEnum()) ? true : isFinal),
+                isStatic, isSynthetic, kind, position, annotations);
+
+        // The underlying MethodDoc for an interface's declared methods winds up being marked
+        // non-abstract.  Correct that here by looking at the immediate-parent class, and marking
+        // this method abstract if it is an unimplemented interface method. 
+        if (containingClass.isInterface()) {
+            isAbstract = true;
+        }
+
+        mReasonOpened = "0:0";
+        mIsAnnotationElement = isAnnotationElement;
+        mTypeParameters = typeParameters;
+        mIsAbstract = isAbstract;
+        mIsSynchronized = isSynchronized;
+        mIsNative = isNative;
+        mFlatSignature = flatSignature;
+        mOverriddenMethod = overriddenMethod;
+        mReturnType = returnType;
+        mParameters = parameters;
+        mThrownExceptions = thrownExceptions;
+    }
+
+    public void init(AnnotationValueInfo defaultAnnotationElementValue)
+    {
+        mDefaultAnnotationElementValue = defaultAnnotationElementValue;
+    }
+
+    public boolean isAbstract()
+    {
+        return mIsAbstract;
+    }
+
+    public boolean isSynchronized()
+    {
+        return mIsSynchronized;
+    }
+
+    public boolean isNative()
+    {
+        return mIsNative;
+    }
+
+    public String flatSignature()
+    {
+        return mFlatSignature;
+    }
+
+    public InheritedTags inlineTags()
+    {
+        return new InlineTags();
+    }
+
+    public InheritedTags firstSentenceTags()
+    {
+        return new FirstSentenceTags();
+    }
+
+    public InheritedTags returnTags() {
+        return new ReturnTags();
+    }
+
+    public TypeInfo returnType()
+    {
+        return mReturnType;
+    }
+
+    public String prettySignature()
+    {
+        String s = "(";
+        int N = mParameters.length;
+        for (int i=0; i<N; i++) {
+            ParameterInfo p = mParameters[i];
+            TypeInfo t = p.type();
+            if (t.isPrimitive()) {
+                s += t.simpleTypeName();
+            } else {
+                s += t.asClassInfo().name();
+            }
+            if (i != N-1) {
+                s += ',';
+            }
+        }
+        s += ')';
+        return s;
+    }
+
+    private boolean inList(ClassInfo item, ThrowsTagInfo[] list)
+    {
+        int len = list.length;
+        String qn = item.qualifiedName();
+        for (int i=0; i<len; i++) {
+            ClassInfo ex = list[i].exception();
+            if (ex != null && ex.qualifiedName().equals(qn)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ThrowsTagInfo[] throwsTags()
+    {
+        if (mThrowsTags == null) {
+            ThrowsTagInfo[] documented = comment().throwsTags();
+            ArrayList<ThrowsTagInfo> rv = new ArrayList<ThrowsTagInfo>();
+
+            int len = documented.length;
+            for (int i=0; i<len; i++) {
+                rv.add(documented[i]);
+            }
+
+            ClassInfo[] all = mThrownExceptions;
+            len = all.length;
+            for (int i=0; i<len; i++) {
+                ClassInfo cl = all[i];
+                if (documented == null || !inList(cl, documented)) {
+                    rv.add(new ThrowsTagInfo("@throws", "@throws",
+                                        cl.qualifiedName(), cl, "",
+                                        containingClass(), position()));
+                }
+            }
+            mThrowsTags = rv.toArray(new ThrowsTagInfo[rv.size()]);
+        }
+        return mThrowsTags;
+    }
+
+    private static int indexOfParam(String name, String[] list)
+    {
+        final int N = list.length;
+        for (int i=0; i<N; i++) {
+            if (name.equals(list[i])) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public ParamTagInfo[] paramTags()
+    {
+        if (mParamTags == null) {
+            final int N = mParameters.length;
+
+            String[] names = new String[N];
+            String[] comments = new String[N];
+            SourcePositionInfo[] positions = new SourcePositionInfo[N];
+
+            // get the right names so we can handle our names being different from
+            // our parent's names.
+            for (int i=0; i<N; i++) {
+                names[i] = mParameters[i].name();
+                comments[i] = "";
+                positions[i] = mParameters[i].position();
+            }
+
+            // gather our comments, and complain about misnamed @param tags
+            for (ParamTagInfo tag: comment().paramTags()) {
+                int index = indexOfParam(tag.parameterName(), names);
+                if (index >= 0) {
+                    comments[index] = tag.parameterComment();
+                    positions[index] = tag.position();
+                } else {
+                    Errors.error(Errors.UNKNOWN_PARAM_TAG_NAME, tag.position(),
+                            "@param tag with name that doesn't match the parameter list: '"
+                            + tag.parameterName() + "'");
+                }
+            }
+             
+            // get our parent's tags to fill in the blanks
+            MethodInfo overridden = this.findOverriddenMethod(name(), signature());
+            if (overridden != null) {
+                ParamTagInfo[] maternal = overridden.paramTags();
+                for (int i=0; i<N; i++) {
+                    if (comments[i].equals("")) {
+                        comments[i] = maternal[i].parameterComment();
+                        positions[i] = maternal[i].position();
+                    }
+                }
+            }
+
+            // construct the results, and cache them for next time
+            mParamTags = new ParamTagInfo[N];
+            for (int i=0; i<N; i++) {
+                mParamTags[i] = new ParamTagInfo("@param", "@param", names[i] + " " + comments[i],
+                        parent(), positions[i]);
+
+                // while we're here, if we find any parameters that are still undocumented at this
+                // point, complain. (this warning is off by default, because it's really, really
+                // common; but, it's good to be able to enforce it)
+                if (comments[i].equals("")) {
+                    Errors.error(Errors.UNDOCUMENTED_PARAMETER, positions[i],
+                            "Undocumented parameter '" + names[i] + "' on method '"
+                            + name() + "'");
+                }
+            }
+        }
+        return mParamTags;
+    }
+
+    public SeeTagInfo[] seeTags()
+    {
+        SeeTagInfo[] result = comment().seeTags();
+        if (result == null) {
+            if (mOverriddenMethod != null) {
+                result = mOverriddenMethod.seeTags();
+            }
+        }
+        return result;
+    }
+
+    public TagInfo[] deprecatedTags()
+    {
+        TagInfo[] result = comment().deprecatedTags();
+        if (result.length == 0) {
+            if (comment().undeprecateTags().length == 0) {
+                if (mOverriddenMethod != null) {
+                    result = mOverriddenMethod.deprecatedTags();
+                }
+            }
+        }
+        return result;
+    }
+
+    public ParameterInfo[] parameters()
+    {
+        return mParameters;
+    }
+    
+
+    public boolean matchesParams(String[] params, String[] dimensions)
+    {
+        if (mParamStrings == null) {
+            ParameterInfo[] mine = mParameters;
+            int len = mine.length;
+            if (len != params.length) {
+                return false;
+            }
+            for (int i=0; i<len; i++) {
+                TypeInfo t = mine[i].type();
+                if (!t.dimension().equals(dimensions[i])) {
+                    return false;
+                }
+                String qn = t.qualifiedTypeName();
+                String s = params[i];
+                int slen = s.length();
+                int qnlen = qn.length();
+                if (!(qn.equals(s) ||
+                        ((slen+1)<qnlen && qn.charAt(qnlen-slen-1)=='.'
+                         && qn.endsWith(s)))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".kind", kind());
+        data.setValue(base + ".name", name());
+        data.setValue(base + ".href", htmlPage());
+        data.setValue(base + ".anchor", anchor());
+
+        if (mReturnType != null) {
+            returnType().makeHDF(data, base + ".returnType", false, typeVariables());
+            data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
+        }
+
+        data.setValue(base + ".synchronized", mIsSynchronized ? "synchronized" : "");
+        data.setValue(base + ".final", isFinal() ? "final" : "");
+        data.setValue(base + ".static", isStatic() ? "static" : "");
+
+        TagInfo.makeHDF(data, base + ".shortDescr", firstSentenceTags());
+        TagInfo.makeHDF(data, base + ".descr", inlineTags());
+        TagInfo.makeHDF(data, base + ".deprecated", deprecatedTags());
+        TagInfo.makeHDF(data, base + ".seeAlso", seeTags());
+        ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
+        AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
+        ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
+        ParameterInfo.makeHDF(data, base + ".params", parameters(), isVarArgs(), typeVariables());
+        if (isProtected()) {
+            data.setValue(base + ".scope", "protected");
+        }
+        else if (isPublic()) {
+            data.setValue(base + ".scope", "public");
+        }
+        TagInfo.makeHDF(data, base + ".returns", returnTags());
+
+        if (mTypeParameters != null) {
+            TypeInfo.makeHDF(data, base + ".generic.typeArguments", mTypeParameters, false);
+        }
+    }
+
+    public HashSet<String> typeVariables()
+    {
+        HashSet<String> result = TypeInfo.typeVariables(mTypeParameters);
+        ClassInfo cl = containingClass();
+        while (cl != null) {
+            TypeInfo[] types = cl.asTypeInfo().typeArguments();
+            if (types != null) {
+                TypeInfo.typeVariables(types, result);
+            }
+            cl = cl.containingClass();
+        }
+        return result;
+    }
+
+    public boolean isExecutable()
+    {
+        return true;
+    }
+
+    public ClassInfo[] thrownExceptions()
+    {
+        return mThrownExceptions;
+    }
+
+    public String typeArgumentsName(HashSet<String> typeVars)
+    {
+        if (mTypeParameters == null || mTypeParameters.length == 0) {
+            return "";
+        } else {
+            return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
+        }
+    }
+
+    public boolean isAnnotationElement()
+    {
+        return mIsAnnotationElement;
+    }
+
+    public AnnotationValueInfo defaultAnnotationElementValue()
+    {
+        return mDefaultAnnotationElementValue;
+    }
+    
+    public void setVarargs(boolean set){
+        mIsVarargs = set;
+    }
+    public boolean isVarArgs(){
+      return mIsVarargs;
+    }
+    public String toString(){
+      return this.name();
+    }
+    
+    public void setReason(String reason) {
+        mReasonOpened = reason;
+    }
+    
+    public String getReason() {
+        return mReasonOpened;
+    }
+
+    private String mFlatSignature;
+    private MethodInfo mOverriddenMethod;
+    private TypeInfo mReturnType;
+    private boolean mIsAnnotationElement;
+    private boolean mIsAbstract;
+    private boolean mIsSynchronized;
+    private boolean mIsNative;
+    private boolean mIsVarargs;
+    private boolean mDeprecatedKnown;
+    private boolean mIsDeprecated;
+    private ParameterInfo[] mParameters;
+    private ClassInfo[] mThrownExceptions;
+    private String[] mParamStrings;
+    ThrowsTagInfo[] mThrowsTags;
+    private ParamTagInfo[] mParamTags;
+    private TypeInfo[] mTypeParameters;
+    private AnnotationValueInfo mDefaultAnnotationElementValue;
+    private String mReasonOpened;
+}
+
diff --git a/tools/droiddoc/src/NavTree.java b/tools/droiddoc/src/NavTree.java
new file mode 100644
index 0000000..9eef0ce
--- /dev/null
+++ b/tools/droiddoc/src/NavTree.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+
+import java.util.ArrayList;
+
+public class NavTree {
+
+    public static void writeNavTree(String dir) {
+        ArrayList<Node> children = new ArrayList();
+        for (PackageInfo pkg: DroidDoc.choosePackages()) {
+            children.add(makePackageNode(pkg));
+        }
+        Node node = new Node("Reference", dir + "packages.html", children);
+
+        StringBuilder buf = new StringBuilder();
+        if (false) {
+            // if you want a root node
+            buf.append("[");
+            node.render(buf);
+            buf.append("]");
+        } else {
+            // if you don't want a root node
+            node.renderChildren(buf);
+        }
+
+        HDF data = DroidDoc.makeHDF();
+        data.setValue("reference_tree", buf.toString());
+        ClearPage.write(data, "navtree_data.cs", "navtree_data.js");
+    }
+
+    private static Node makePackageNode(PackageInfo pkg) {
+        ArrayList<Node> children = new ArrayList();
+
+        children.add(new Node("Description", pkg.fullDescriptionHtmlPage(), null));
+
+        addClassNodes(children, "Interfaces", pkg.interfaces());
+        addClassNodes(children, "Classes", pkg.ordinaryClasses());
+        addClassNodes(children, "Enums", pkg.enums());
+        addClassNodes(children, "Exceptions", pkg.exceptions());
+        addClassNodes(children, "Errors", pkg.errors());
+
+        return new Node(pkg.name(), pkg.htmlPage(), children);
+    }
+
+    private static void addClassNodes(ArrayList<Node> parent, String label, ClassInfo[] classes) {
+        ArrayList<Node> children = new ArrayList();
+
+        for (ClassInfo cl: classes) {
+            if (cl.checkLevel()) {
+                children.add(new Node(cl.name(), cl.htmlPage(), null));
+            }
+        }
+
+        if (children.size() > 0) {
+            parent.add(new Node(label, null, children));
+        }
+    }
+
+    private static class Node {
+        private String mLabel;
+        private String mLink;
+        ArrayList<Node> mChildren;
+
+        Node(String label, String link, ArrayList<Node> children) {
+            mLabel = label;
+            mLink = link;
+            mChildren = children;
+        }
+
+        static void renderString(StringBuilder buf, String s) {
+            if (s == null) {
+                buf.append("null");
+            } else {
+                buf.append('"');
+                final int N = s.length();
+                for (int i=0; i<N; i++) {
+                    char c = s.charAt(i);
+                    if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
+                        buf.append(c);
+                    } else {
+                        buf.append("\\u");
+                        for (int j=0; i<4; i++) {
+                            char x = (char)(c & 0x000f);
+                            if (x > 10) {
+                                x = (char)(x - 10 + 'a');
+                            } else {
+                                x = (char)(x + '0');
+                            }
+                            buf.append(x);
+                            c >>= 4;
+                        }
+                    }
+                }
+                buf.append('"');
+            }
+        }
+
+        void renderChildren(StringBuilder buf) {
+            ArrayList<Node> list = mChildren;
+            if (list == null || list.size() == 0) {
+                // We output null for no children.  That way empty lists here can just
+                // be a byproduct of how we generate the lists.
+                buf.append("null");
+            } else {
+                buf.append("[ ");
+                final int N = list.size();
+                for (int i=0; i<N; i++) {
+                    list.get(i).render(buf);
+                    if (i != N-1) {
+                        buf.append(", ");
+                    }
+                }
+                buf.append(" ]\n");
+            }
+        }
+
+        void render(StringBuilder buf) {
+            buf.append("[ ");
+            renderString(buf, mLabel);
+            buf.append(", ");
+            renderString(buf, mLink);
+            buf.append(", ");
+            renderChildren(buf);
+            buf.append(" ]");
+        }
+    }
+}
diff --git a/tools/droiddoc/src/PackageInfo.java b/tools/droiddoc/src/PackageInfo.java
new file mode 100644
index 0000000..aac0def
--- /dev/null
+++ b/tools/droiddoc/src/PackageInfo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.sun.javadoc.*;
+import com.sun.tools.doclets.*;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public class PackageInfo extends DocInfo implements ContainerInfo
+{
+    public static final Comparator<PackageInfo> comparator = new Comparator<PackageInfo>() {
+        public int compare(PackageInfo a, PackageInfo b) {
+            return a.name().compareTo(b.name());
+        }
+    };
+
+    public PackageInfo(PackageDoc pkg, String name, SourcePositionInfo position)
+    {
+        super(pkg.getRawCommentText(), position);
+        mName = name;
+
+        if (pkg == null) {
+            throw new RuntimeException("pkg is null");
+        }
+        mPackage = pkg;
+    }
+
+    public String htmlPage()
+    {
+        String s = mName;
+        s = s.replace('.', '/');
+        s += "/package-summary.html";
+        s = DroidDoc.javadocDir + s;
+        return s;
+    }
+
+    public String fullDescriptionHtmlPage() {
+        String s = mName;
+        s = s.replace('.', '/');
+        s += "/package-descr.html";
+        s = DroidDoc.javadocDir + s;
+        return s;
+    }
+
+    public ContainerInfo parent()
+    {
+        return null;
+    }
+
+    public boolean isHidden()
+    {
+        return comment().isHidden();
+    }
+
+    public boolean checkLevel() {
+        // TODO should return false if all classes are hidden but the package isn't.
+        // We don't have this so I'm not doing it now.
+        return !isHidden();
+    }
+
+    public String name()
+    {
+        return mName;
+    }
+
+    public String qualifiedName()
+    {
+        return mName;
+    }
+
+    public TagInfo[] inlineTags()
+    {
+        return comment().tags();
+    }
+
+    public TagInfo[] firstSentenceTags()
+    {
+        return comment().briefTags();
+    }
+
+    public static ClassInfo[] filterHidden(ClassInfo[] classes)
+    {
+        ArrayList<ClassInfo> out = new ArrayList<ClassInfo>();
+
+        for (ClassInfo cl: classes) {
+            if (!cl.isHidden()) {
+                out.add(cl);
+            }
+        }
+
+        return out.toArray(new ClassInfo[0]);
+    }
+
+    public void makeLink(HDF data, String base)
+    {
+        if (checkLevel()) {
+            data.setValue(base + ".link", htmlPage());
+        }
+        data.setValue(base + ".name", name());
+    }
+
+    public void makeClassLinkListHDF(HDF data, String base)
+    {
+        makeLink(data, base);
+        ClassInfo.makeLinkListHDF(data, base + ".interfaces", interfaces());
+        ClassInfo.makeLinkListHDF(data, base + ".classes", ordinaryClasses());
+        ClassInfo.makeLinkListHDF(data, base + ".enums", enums());
+        ClassInfo.makeLinkListHDF(data, base + ".exceptions", exceptions());
+        ClassInfo.makeLinkListHDF(data, base + ".errors", errors());
+    }
+
+    public ClassInfo[] interfaces()
+    {
+        if (mInterfaces == null) {
+            mInterfaces = ClassInfo.sortByName(filterHidden(Converter.convertClasses(
+                            mPackage.interfaces())));
+        }
+        return mInterfaces;
+    }
+
+    public ClassInfo[] ordinaryClasses()
+    {
+        if (mOrdinaryClasses == null) {
+            mOrdinaryClasses = ClassInfo.sortByName(filterHidden(Converter.convertClasses(
+                            mPackage.ordinaryClasses())));
+        }
+        return mOrdinaryClasses;
+    }
+
+    public ClassInfo[] enums()
+    {
+        if (mEnums == null) {
+            mEnums = ClassInfo.sortByName(filterHidden(Converter.convertClasses(mPackage.enums())));
+        }
+        return mEnums;
+    }
+
+    public ClassInfo[] exceptions()
+    {
+        if (mExceptions == null) {
+            mExceptions = ClassInfo.sortByName(filterHidden(Converter.convertClasses(
+                        mPackage.exceptions())));
+        }
+        return mExceptions;
+    }
+
+    public ClassInfo[] errors()
+    {
+        if (mErrors == null) {
+            mErrors = ClassInfo.sortByName(filterHidden(Converter.convertClasses(
+                        mPackage.errors())));
+        }
+        return mErrors;
+    }
+
+    // in hashed containers, treat the name as the key
+    @Override
+    public int hashCode() {
+        return mName.hashCode();
+    }
+
+    private String mName;
+    private PackageDoc mPackage;
+    private ClassInfo[] mInterfaces;
+    private ClassInfo[] mOrdinaryClasses;
+    private ClassInfo[] mEnums;
+    private ClassInfo[] mExceptions;
+    private ClassInfo[] mErrors;
+}
+
diff --git a/tools/droiddoc/src/ParamTagInfo.java b/tools/droiddoc/src/ParamTagInfo.java
new file mode 100644
index 0000000..c21ecd5
--- /dev/null
+++ b/tools/droiddoc/src/ParamTagInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+public class ParamTagInfo extends ParsedTagInfo
+{
+    static final Pattern PATTERN = Pattern.compile(
+                                "([^ \t\r\n]+)[ \t\r\n]+(.*)",
+                                Pattern.DOTALL);
+
+    private boolean mIsTypeParameter;
+    private String mParameterComment;
+    private String mParameterName;
+
+    ParamTagInfo(String name, String kind, String text, ContainerInfo base,
+            SourcePositionInfo sp)
+    {
+        super(name, kind, text, base, sp);
+
+        Matcher m = PATTERN.matcher(text);
+        if (m.matches()) {
+            mParameterName = m.group(1);
+            mParameterComment = m.group(2);
+            int len = mParameterName.length();
+            mIsTypeParameter = len > 2
+                                && mParameterName.charAt(0) == '<'
+                                && mParameterName.charAt(len-1) == '>';
+        } else {
+            mParameterName = text.trim();
+            mParameterComment = "";
+            mIsTypeParameter = false;
+        }
+        setCommentText(mParameterComment);
+    }
+
+    ParamTagInfo(String name, String kind, String text,
+                            boolean isTypeParameter, String parameterComment,
+                            String parameterName, ContainerInfo base,
+                            SourcePositionInfo sp)
+    {
+        super(name, kind, text, base, sp);
+        mIsTypeParameter = isTypeParameter;
+        mParameterComment = parameterComment;
+        mParameterName = parameterName;
+    }
+
+    public boolean isTypeParameter()
+    {
+        return mIsTypeParameter;
+    }
+
+    public String parameterComment()
+    {
+        return mParameterComment;
+    }
+
+    public String parameterName()
+    {
+        return mParameterName;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".name", parameterName());
+        data.setValue(base + ".isTypeParameter", isTypeParameter() ? "1" : "0");
+        TagInfo.makeHDF(data, base + ".comment", commentTags());
+    }
+
+    public static void makeHDF(HDF data, String base, ParamTagInfo[] tags)
+    {
+        for (int i=0; i<tags.length; i++) {
+            // don't output if the comment is ""
+            if (!"".equals(tags[i].parameterComment())) {
+                tags[i].makeHDF(data, base + "." + i);
+            }
+        }
+    }
+}
diff --git a/tools/droiddoc/src/ParameterInfo.java b/tools/droiddoc/src/ParameterInfo.java
new file mode 100644
index 0000000..44608be
--- /dev/null
+++ b/tools/droiddoc/src/ParameterInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.HashSet;
+
+public class ParameterInfo
+{
+    ParameterInfo(String name, String typeName, TypeInfo type, SourcePositionInfo position)
+    {
+        mName = name;
+        mTypeName = typeName;
+        mType = type;
+        mPosition = position;
+    }
+
+    TypeInfo type()
+    {
+        return mType;
+    }
+
+    String name()
+    {
+        return mName;
+    }
+
+    String typeName()
+    {
+        return mTypeName;
+    }
+
+    SourcePositionInfo position()
+    {
+        return mPosition;
+    }
+
+    public void makeHDF(HDF data, String base, boolean isLastVararg,
+            HashSet<String> typeVariables)
+    {
+        data.setValue(base + ".name", this.name());
+        type().makeHDF(data, base + ".type", isLastVararg, typeVariables);
+    }
+
+    public static void makeHDF(HDF data, String base, ParameterInfo[] params,
+            boolean isVararg, HashSet<String> typeVariables)
+    {
+        for (int i=0; i<params.length; i++) {
+            params[i].makeHDF(data, base + "." + i,
+                    isVararg && (i == params.length - 1), typeVariables);
+        }
+    }
+    
+    String mName;
+    String mTypeName;
+    TypeInfo mType;
+    SourcePositionInfo mPosition;
+}
+
diff --git a/tools/droiddoc/src/ParsedTagInfo.java b/tools/droiddoc/src/ParsedTagInfo.java
new file mode 100755
index 0000000..c2e4806
--- /dev/null
+++ b/tools/droiddoc/src/ParsedTagInfo.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.ArrayList;
+
+public class ParsedTagInfo extends TagInfo
+{
+    private ContainerInfo mContainer;
+    private String mCommentText;
+    private Comment mComment;
+
+    ParsedTagInfo(String name, String kind, String text, ContainerInfo base, SourcePositionInfo sp)
+    {
+        super(name, kind, text, SourcePositionInfo.findBeginning(sp, text));
+        mContainer = base;
+        mCommentText = text;
+    }
+
+    public TagInfo[] commentTags()
+    {
+        if (mComment == null) {
+            mComment = new Comment(mCommentText, mContainer, position());
+        }
+        return mComment.tags();
+    }
+
+    protected void setCommentText(String comment)
+    {
+        mCommentText = comment;
+    }
+
+    public static <T extends ParsedTagInfo> TagInfo[]
+    joinTags(T[] tags)
+    {
+        ArrayList<TagInfo> list = new ArrayList<TagInfo>();
+        final int N = tags.length;
+        for (int i=0; i<N; i++) {
+            TagInfo[] t = tags[i].commentTags();
+            final int M = t.length;
+            for (int j=0; j<M; j++) {
+                list.add(t[j]);
+            }
+        }
+        return list.toArray(new TagInfo[list.size()]);
+    }
+}
diff --git a/tools/droiddoc/src/Proofread.java b/tools/droiddoc/src/Proofread.java
new file mode 100644
index 0000000..ec9f523
--- /dev/null
+++ b/tools/droiddoc/src/Proofread.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.IOException;
+import java.io.FileWriter;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class Proofread
+{
+    static FileWriter out = null;
+    static final Pattern WHITESPACE = Pattern.compile("\\r?\\n");
+    static final String INDENT = "        ";
+    static final String NEWLINE = "\n" + INDENT;
+
+    public static void initProofread(String filename)
+    {
+        try {
+            out = new FileWriter(filename);
+            out.write("javadoc proofread file: " + filename + "\n");
+        }
+        catch (IOException e) {
+            if (out != null) {
+                try {
+                    out.close();
+                }
+                catch (IOException ex) {
+                }
+                out = null;
+            }
+            System.err.println("error opening file: " + filename);
+        }
+    }
+
+    public static void finishProofread(String filename)
+    {
+        if (out == null) {
+            return;
+        }
+
+        try {
+            out.close();
+        }
+        catch (IOException e) {
+        }
+    }
+
+    public static void write(String s)
+    {
+        if (out == null) {
+            return ;
+        }
+        try {
+            out.write(s);
+        }
+        catch (IOException e) {
+        }
+    }
+
+    public static void writeIndented(String s)
+    {
+        s = s.trim();
+        Matcher m = WHITESPACE.matcher(s);
+        s = m.replaceAll(NEWLINE);
+        write(INDENT);
+        write(s);
+        write("\n");
+    }
+
+    public static void writeFileHeader(String filename)
+    {
+        write("\n\n=== ");
+        write(filename);
+        write(" ===\n");
+    }
+
+    public static void writeTagList(TagInfo[] tags)
+    {
+        if (out == null) {
+            return;
+        }
+
+        for (TagInfo t: tags) {
+            String k = t.kind();
+            if ("Text".equals(t.name())) {
+                writeIndented(t.text());
+            }
+            else if ("@more".equals(k)) {
+                writeIndented("");
+            }
+            else if ("@see".equals(k)) {
+                SeeTagInfo see = (SeeTagInfo)t;
+                String label = see.label();
+                if (label == null) {
+                    label = "";
+                }
+                writeIndented("{" + see.name() + " ... " + label + "}");
+            }
+            else if ("@code".equals(k)) {
+                writeIndented(t.text());
+            }
+            else if ("@samplecode".equals(k)) {
+                writeIndented(t.text());
+            }
+            else {
+                writeIndented("{" + (t.name() != null ? t.name() : "") + "/" +
+                        t.text() + "}");
+            }
+        }
+    }
+
+    public static void writePackages(String filename, TagInfo[] tags)
+    {
+        if (out == null) {
+            return;
+        }
+
+        writeFileHeader(filename);
+        writeTagList(tags);
+    }
+
+    public static void writePackage(String filename, TagInfo[] tags)
+    {
+        if (out == null) {
+            return;
+        }
+
+        writeFileHeader(filename);
+        writeTagList(tags);
+    }
+
+    public static void writeClass(String filename, ClassInfo cl)
+    {
+        if (out == null) {
+            return;
+        }
+
+        writeFileHeader(filename);
+        writeTagList(cl.inlineTags());
+
+        // enum constants
+        for (FieldInfo f: cl.enumConstants()) {
+            write("ENUM: " + f.name() + "\n");
+            writeTagList(f.inlineTags());
+        }
+
+        // fields
+        for (FieldInfo f: cl.selfFields()) {
+            write("FIELD: " + f.name() + "\n");
+            writeTagList(f.inlineTags());
+        }
+
+        // constructors
+        for (MethodInfo m: cl.constructors()) {
+            write("CONSTRUCTOR: " + m.name() + "\n");
+            writeTagList(m.inlineTags().tags());
+        }
+
+        // methods
+        for (MethodInfo m: cl.selfMethods()) {
+            write("METHOD: " + m.name() + "\n");
+            writeTagList(m.inlineTags().tags());
+        }
+    }
+}
diff --git a/tools/droiddoc/src/SampleCode.java b/tools/droiddoc/src/SampleCode.java
new file mode 100644
index 0000000..e2283bd
--- /dev/null
+++ b/tools/droiddoc/src/SampleCode.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+
+public class SampleCode {
+    String mSource;
+    String mDest;
+    String mTitle;
+
+    public SampleCode(String source, String dest, String title) {
+        mSource = source;
+        mTitle = title;
+        int len = dest.length();
+        if (len > 1 && dest.charAt(len-1) != '/') {
+            mDest = dest + '/';
+        } else {
+            mDest = dest;
+        }
+    }
+
+    public void write() {
+        File f = new File(mSource);
+        if (!f.isDirectory()) {
+            System.out.println("-samplecode not a directory: " + mSource);
+            return;
+        }
+        writeDirectory(f, mDest);
+    }
+
+    public static String convertExtension(String s, String ext) {
+        return s.substring(0, s.lastIndexOf('.')) + ext;
+    }
+
+    public static String[] IMAGES = { ".png", ".jpg", ".gif" };
+    public static String[] TEMPLATED = { ".java", ".xml" };
+
+    public static boolean inList(String s, String[] list) {
+        for (String t: list) {
+            if (s.endsWith(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void writeDirectory(File dir, String relative) {
+        TreeSet<String> dirs = new TreeSet<String>();
+        TreeSet<String> files = new TreeSet<String>();
+
+        String subdir = relative; //.substring(mDest.length());
+
+        for (File f: dir.listFiles()) {
+            String name = f.getName();
+            if (name.startsWith(".") || name.startsWith("_")) {
+                continue;
+            }
+            if (f.isFile()) {
+                String out = relative + name;
+
+                if (inList(out, IMAGES)) {
+                    // copied directly
+                    ClearPage.copyFile(f, out);
+                    writeImagePage(f, convertExtension(out, DroidDoc.htmlExtension), subdir);
+                    files.add(name);
+                }
+                if (inList(out, TEMPLATED)) {
+                    // copied and goes through the template
+                    ClearPage.copyFile(f, out);
+                    writePage(f, convertExtension(out, DroidDoc.htmlExtension), subdir);
+                    files.add(name);
+                }
+                // else ignored
+            }
+            else if (f.isDirectory()) {
+                writeDirectory(f, relative + name + "/");
+                dirs.add(name);
+            }
+        }
+
+        // write the index page
+        int i;
+        HDF hdf = DroidDoc.makeHDF();
+
+        hdf.setValue("page.title", dir.getName() + " - " + mTitle);
+        hdf.setValue("projectTitle", mTitle);
+        hdf.setValue("subdir", subdir);
+        i=0;
+        for (String d: dirs) {
+            hdf.setValue("subdirs." + i + ".name", d);
+            i++;
+        }
+        i=0;
+        for (String f: files) {
+            hdf.setValue("files." + i + ".name", f);
+            hdf.setValue("files." + i + ".href", convertExtension(f, ".html"));
+            i++;
+        }
+        String filename = dir.getPath() + "/_index.html";
+        String summary = SampleTagInfo.readFile(new SourcePositionInfo(filename, -1,-1), filename,
+                                                "sample code", true, false, true);
+        if (summary == null) {
+            summary = "";
+        }
+        hdf.setValue("summary", summary);
+        
+        ClearPage.write(hdf, "sampleindex.cs", relative + "/index" + DroidDoc.htmlExtension);
+    }
+
+    public void writePage(File f, String out, String subdir) {
+        String name = f.getName();
+
+        String filename = f.getPath();
+        String data = SampleTagInfo.readFile(new SourcePositionInfo(filename, -1,-1), filename,
+                                                "sample code", true, true, true);
+        data = DroidDoc.escape(data);
+        
+        HDF hdf = DroidDoc.makeHDF();
+
+        hdf.setValue("page.title", name);
+        hdf.setValue("subdir", subdir);
+        hdf.setValue("realFile", name);
+        hdf.setValue("fileContents", data);
+
+        ClearPage.write(hdf, "sample.cs", out);
+    }
+
+    public void writeImagePage(File f, String out, String subdir) {
+        String name = f.getName();
+
+        String data = "<img src=\"" + name + "\" title=\"" + name + "\" />";
+        
+        HDF hdf = DroidDoc.makeHDF();
+
+        hdf.setValue("page.title", name);
+        hdf.setValue("subdir", subdir);
+        hdf.setValue("realFile", name);
+        hdf.setValue("fileContents", data);
+
+        ClearPage.write(hdf, "sample.cs", out);
+    }
+}
diff --git a/tools/droiddoc/src/SampleTagInfo.java b/tools/droiddoc/src/SampleTagInfo.java
new file mode 100644
index 0000000..c80083b
--- /dev/null
+++ b/tools/droiddoc/src/SampleTagInfo.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+import java.io.Reader;
+import java.io.IOException;
+import java.io.FileReader;
+import java.io.LineNumberReader;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/*
+ * SampleTagInfo copies text from a given file into the javadoc comment.
+ *
+ * The @include tag copies the text verbatim from the given file.
+ *
+ * The @sample tag copies the text from the given file, stripping leading and
+ * trailing whitespace, and reducing the indent level of the text to the indent
+ * level of the first non-whitespace line.
+ *
+ * Both tags accept either a filename and an id or just a filename.  If no id
+ * is provided, the entire file is copied.  If an id is provided, the lines
+ * in the given file between the first two lines containing BEGIN_INCLUDE(id)
+ * and END_INCLUDE(id), for the given id, are copied.  The id may be only 
+ * letters, numbers and underscore (_).
+ *
+ * Four examples:
+ * {@include samples/ApiDemos/src/com/google/app/Notification1.java}
+ * {@sample samples/ApiDemos/src/com/google/app/Notification1.java}
+ * {@include samples/ApiDemos/src/com/google/app/Notification1.java Bleh}
+ * {@sample samples/ApiDemos/src/com/google/app/Notification1.java Bleh}
+ *
+ */
+public class SampleTagInfo extends TagInfo
+{
+    static final int STATE_BEGIN = 0;
+    static final int STATE_MATCHING = 1;
+
+    static final Pattern TEXT = Pattern.compile(
+                "[\r\n \t]*([^\r\n \t]*)[\r\n \t]*([0-9A-Za-z_]*)[\r\n \t]*",
+                Pattern.DOTALL);
+
+    private static final String BEGIN_INCLUDE = "BEGIN_INCLUDE";
+    private static final String END_INCLUDE = "END_INCLUDE";
+
+    private ContainerInfo mBase;
+    private String mIncluded;
+
+    public static String escapeHtml(String str) {
+        return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
+    }
+
+    private static boolean isIncludeLine(String str) {
+        return str.indexOf(BEGIN_INCLUDE)>=0 || str.indexOf(END_INCLUDE)>=0;
+    }
+
+    SampleTagInfo(String name, String kind, String text, ContainerInfo base,
+            SourcePositionInfo position)
+    {
+        super(name, kind, text, position);
+        mBase = base;
+
+        Matcher m = TEXT.matcher(text);
+        if (!m.matches()) {
+            Errors.error(Errors.BAD_INCLUDE_TAG, position, "Bad @include tag: "
+                    + text);
+            return;
+        }
+        String filename = m.group(1);
+        String id = m.group(2);
+        boolean trim = "@sample".equals(name);
+
+        if (id == null || "".equals(id)) {
+            mIncluded = readFile(position, filename, id, trim, true, false);
+        } else {
+            mIncluded = loadInclude(position, filename, id, trim);
+        }
+
+        if (mIncluded == null) {
+            Errors.error(Errors.BAD_INCLUDE_TAG, position, "include tag '" + id
+                    + "' not found in file: " + filename);
+        }
+    }
+
+    static String getTrimString(String line)
+    {
+        int i = 0;
+        int len = line.length();
+        for (; i<len; i++) {
+            char c = line.charAt(i);
+            if (c != ' ' && c != '\t') {
+                break;
+            }
+        }
+        if (i == len) {
+            return null;
+        } else {
+            return line.substring(0, i);
+        }
+    }
+
+    static String loadInclude(SourcePositionInfo pos, String filename,
+                                String id, boolean trim)
+    {
+        Reader input = null;
+        StringBuilder result = new StringBuilder();
+
+        String begin = BEGIN_INCLUDE + "(" + id + ")";
+        String end = END_INCLUDE + "(" + id + ")";
+
+        try {
+            input = new FileReader(filename);
+            LineNumberReader lines = new LineNumberReader(input);
+
+            int state = STATE_BEGIN;
+
+            int trimLength = -1;
+            String trimString = null;
+            int trailing = 0;
+
+            while (true) {
+                String line = lines.readLine();
+                if (line == null) {
+                    return null;
+                }
+                switch (state) {
+                case STATE_BEGIN:
+                    if (line.indexOf(begin) >= 0) {
+                        state = STATE_MATCHING;
+                    }
+                    break;
+                case STATE_MATCHING:
+                    if (line.indexOf(end) >= 0) {
+                        return result.substring(0);
+                    } else {
+                        boolean empty = "".equals(line.trim());
+                        if (trim) {
+                            if (isIncludeLine(line)) {
+                                continue;
+                            }
+                            if (trimLength < 0 && !empty) {
+                                trimString = getTrimString(line);
+                                if (trimString != null) {
+                                    trimLength = trimString.length();
+                                }
+                            }
+                            if (trimLength >= 0 && line.length() > trimLength) {
+                                boolean trimThisLine = true;
+                                for (int i=0; i<trimLength; i++) {
+                                    if (line.charAt(i) != trimString.charAt(i)){
+                                        trimThisLine = false;
+                                        break;
+                                    }
+                                }
+                                if (trimThisLine) {
+                                    line = line.substring(trimLength);
+                                }
+                            }
+                            if (trimLength >= 0) {
+                                if (!empty) {
+                                    for (int i=0; i<trailing; i++) {
+                                        result.append('\n');
+                                    }
+                                    line = escapeHtml(line);
+                                    result.append(line);
+                                    trailing = 1;  // add \n next time, maybe
+                                } else {
+                                    trailing++;
+                                }
+                            }
+                        } else {
+                            result.append(line);
+                            result.append('\n');
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+        catch (IOException e) {
+            Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for"
+                    + " include \"" + id + "\" " + filename);
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (IOException ex) {
+                }
+            }
+        }
+        Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Did not find " + end
+                + " in file " + filename);
+        return null;
+    }
+
+    static String readFile(SourcePositionInfo pos, String filename,
+                                String id, boolean trim, boolean escape,
+                                boolean errorOk)
+    {
+        Reader input = null;
+        StringBuilder result = new StringBuilder();
+        int trailing = 0;
+        boolean started = false;
+        try {
+            input = new FileReader(filename);
+            LineNumberReader lines = new LineNumberReader(input);
+
+            while (true) {
+                String line = lines.readLine();
+                if (line == null) {
+                    break;
+                }
+                if (trim) {
+                    if (isIncludeLine(line)) {
+                        continue;
+                    }
+                    if (!"".equals(line.trim())) {
+                        if (started) {
+                            for (int i=0; i<trailing; i++) {
+                                result.append('\n');
+                            }
+                        }
+                        if (escape) {
+                            line = escapeHtml(line);
+                        }
+                        result.append(line);
+                        trailing = 1;  // add \n next time, maybe
+                        started = true;
+                    } else {
+                        if (started) {
+                            trailing++;
+                        }
+                    }
+                } else {
+                    result.append(line);
+                    result.append('\n');
+                }
+            }
+        }
+        catch (IOException e) {
+            if (errorOk) {
+                return null;
+            } else {
+                Errors.error(Errors.BAD_INCLUDE_TAG, pos, "Error reading file for"
+                        + " include \"" + id + "\" " + filename);
+            }
+        }
+        finally {
+            if (input != null) {
+                try {
+                    input.close();
+                }
+                catch (IOException ex) {
+                }
+            }
+        }
+        return result.substring(0);
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".name", name());
+        data.setValue(base + ".kind", kind());
+        if (mIncluded != null) {
+            data.setValue(base + ".text", mIncluded);
+        } else {
+            data.setValue(base + ".text", "INCLUDE_ERROR");
+        }
+    }
+}
+
diff --git a/tools/droiddoc/src/Scoped.java b/tools/droiddoc/src/Scoped.java
new file mode 100644
index 0000000..cca61ed
--- /dev/null
+++ b/tools/droiddoc/src/Scoped.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public interface Scoped {
+    boolean isPublic();
+    boolean isProtected();
+    boolean isPackagePrivate();
+    boolean isPrivate();
+    boolean isHidden();
+}
diff --git a/tools/droiddoc/src/SeeTagInfo.java b/tools/droiddoc/src/SeeTagInfo.java
new file mode 100644
index 0000000..94863b5
--- /dev/null
+++ b/tools/droiddoc/src/SeeTagInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.ArrayList;
+
+public class SeeTagInfo extends TagInfo
+{
+    private ContainerInfo mBase;
+    LinkReference mLink;
+
+    SeeTagInfo(String name, String kind, String text, ContainerInfo base,
+            SourcePositionInfo position)
+    {
+        super(name, kind, text, position);
+        mBase = base;
+    }
+
+    protected LinkReference linkReference() {
+        if (mLink == null) {
+            mLink = LinkReference.parse(text(), mBase, position(),
+                           (!"@see".equals(name())) && (mBase != null ? mBase.checkLevel() : true));
+        }
+        return mLink;
+    }
+
+    public String label()
+    {
+        return linkReference().label;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        LinkReference linkRef = linkReference();
+        if (linkRef.kind != null) {
+            // if they have a better suggestion about "kind" use that.
+            // do this before super.makeHDF() so it picks it up
+            setKind(linkRef.kind);
+        }
+
+        super.makeHDF(data, base);
+
+        data.setValue(base + ".label", linkRef.label);
+        if (linkRef.href != null) {
+            data.setValue(base + ".href", linkRef.href);
+        }
+    }
+
+    public boolean checkLevel() {
+        return linkReference().checkLevel();
+    }
+
+    public static void makeHDF(HDF data, String base, SeeTagInfo[] tags)
+    {
+        int j=0;
+        for (SeeTagInfo tag: tags) {
+            if (tag.mBase.checkLevel() && tag.checkLevel()) {
+                tag.makeHDF(data, base + "." + j);
+                j++;
+            }
+        }
+    }
+}
diff --git a/tools/droiddoc/src/Sorter.java b/tools/droiddoc/src/Sorter.java
new file mode 100644
index 0000000..92039d4
--- /dev/null
+++ b/tools/droiddoc/src/Sorter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Sorter implements Comparable
+{
+    public String label;
+    public Object data;
+
+    public Sorter(String l, Object d)
+    {
+        label = l;
+        data = d;
+    }
+
+    public int compareTo(Object other)
+    {
+        return label.compareToIgnoreCase(((Sorter)other).label);
+    }
+}
diff --git a/tools/droiddoc/src/SourcePositionInfo.java b/tools/droiddoc/src/SourcePositionInfo.java
new file mode 100644
index 0000000..6244803
--- /dev/null
+++ b/tools/droiddoc/src/SourcePositionInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class SourcePositionInfo implements Comparable
+{
+    public SourcePositionInfo() {
+        this.file = "<unknown>";
+        this.line = 0;
+        this.column = 0;
+    }
+
+    public SourcePositionInfo(String file, int line, int column)
+    {
+        this.file = file;
+        this.line = line;
+        this.column = column;
+    }
+
+    public SourcePositionInfo(SourcePositionInfo that)
+    {
+        this.file = that.file;
+        this.line = that.line;
+        this.column = that.column;
+    }
+
+    /**
+     * Given this position and str which occurs at that position, as well as str an index into str,
+     * find the SourcePositionInfo.
+     *
+     * @throw StringIndexOutOfBoundsException if index &gt; str.length()
+     */
+    public static SourcePositionInfo add(SourcePositionInfo that, String str, int index)
+    {
+        if (that == null) {
+            return null;
+        }
+        int line = that.line;
+        char prev = 0;
+        for (int i=0; i<index; i++) {
+            char c = str.charAt(i);
+            if (c == '\r' || (c == '\n' && prev != '\r')) {
+                line++;
+            }
+            prev = c;
+        }
+        return new SourcePositionInfo(that.file, line, 0);
+    }
+
+    public static SourcePositionInfo findBeginning(SourcePositionInfo that, String str)
+    {
+        if (that == null) {
+            return null;
+        }
+        int line = that.line-1; // -1 because, well, it seems to work
+        int prev = 0;
+        for (int i=str.length()-1; i>=0; i--) {
+            char c = str.charAt(i);
+            if ((c == '\r' && prev != '\n') || (c == '\n')) {
+                line--;
+            }
+            prev = c;
+        }
+        return new SourcePositionInfo(that.file, line, 0);
+    }
+
+    public String toString()
+    {
+        return file + ':' + line;
+    }
+
+    public int compareTo(Object o) {
+        SourcePositionInfo that = (SourcePositionInfo)o;
+        int r = this.file.compareTo(that.file);
+        if (r != 0) return r;
+        return this.line - that.line;
+    }
+
+    public String file;
+    public int line;
+    public int column;
+}
diff --git a/tools/droiddoc/src/Stubs.java b/tools/droiddoc/src/Stubs.java
new file mode 100644
index 0000000..e1ec76a
--- /dev/null
+++ b/tools/droiddoc/src/Stubs.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.Comparator;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+
+public class Stubs {
+    private static HashSet<ClassInfo> notStrippable;
+    public static void writeStubs(String stubsDir, Boolean writeXML, String xmlFile,
+            HashSet<String> stubPackages) {
+        // figure out which classes we need
+        notStrippable = new HashSet();
+        ClassInfo[] all = Converter.allClasses();
+        File  xml = new File(xmlFile);
+        xml.getParentFile().mkdirs();
+        PrintStream xmlWriter = null;
+        if (writeXML) {
+            try {
+                xmlWriter = new PrintStream(xml);
+            } catch (FileNotFoundException e) {
+                Errors.error(Errors.IO_ERROR, new SourcePositionInfo(xmlFile, 0, 0),
+                        "Cannot open file for write.");
+            }
+        }
+        // If a class is public or protected, not hidden, and marked as included,
+        // then we can't strip it
+        for (ClassInfo cl: all) {
+            if (cl.checkLevel() && cl.isIncluded()) {
+                cantStripThis(cl, notStrippable, "0:0");
+            }
+        }
+
+        // complain about anything that looks includeable but is not supposed to
+        // be written, e.g. hidden things
+        for (ClassInfo cl: notStrippable) {
+            if (!cl.isHidden()) {
+                MethodInfo[] methods = cl.selfMethods();
+                for (MethodInfo m: methods) {
+                    if (m.isHidden()) {
+                        Errors.error(Errors.UNAVAILABLE_SYMBOL,
+                                m.position(), "Reference to hidden method "
+                                + m.name());
+                    } else if (m.isDeprecated()) {
+                        // don't bother reporting deprecated methods
+                        // unless they are public
+                        Errors.error(Errors.DEPRECATED,
+                                m.position(), "Method "
+                                + cl.qualifiedName() + "." + m.name()
+                                + " is deprecated");
+                    }
+
+                    ClassInfo returnClass = m.returnType().asClassInfo();
+                    if (returnClass != null && returnClass.isHidden()) {
+                        Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(),
+                                "Method " + cl.qualifiedName() + "." + m.name()
+                                + " returns unavailable type " + returnClass.name());
+                    }
+
+                    ParameterInfo[] params = m.parameters();
+                    for (ParameterInfo p: params) {
+                        TypeInfo t = p.type();
+                        if (!t.isPrimitive()) {
+                            if (t.asClassInfo().isHidden()) {
+                                Errors.error(Errors.UNAVAILABLE_SYMBOL,
+                                        m.position(), "Parameter of hidden type "
+                                        + t.fullName() + " in "
+                                        + cl.qualifiedName() + "." + m.name() + "()");
+                            }
+                        }
+                    }
+                }
+
+                // annotations are handled like methods
+                methods = cl.annotationElements();
+                for (MethodInfo m: methods) {
+                    if (m.isHidden()) {
+                        Errors.error(Errors.UNAVAILABLE_SYMBOL,
+                                m.position(), "Reference to hidden annotation "
+                                + m.name());
+                    }
+
+                    ClassInfo returnClass = m.returnType().asClassInfo();
+                    if (returnClass != null && returnClass.isHidden()) {
+                        Errors.error(Errors.UNAVAILABLE_SYMBOL,
+                                m.position(), "Annotation '" + m.name()
+                                + "' returns unavailable type " + returnClass.name());
+                    }
+
+                    ParameterInfo[] params = m.parameters();
+                    for (ParameterInfo p: params) {
+                        TypeInfo t = p.type();
+                        if (!t.isPrimitive()) {
+                            if (t.asClassInfo().isHidden()) {
+                                Errors.error(Errors.UNAVAILABLE_SYMBOL,
+                                        p.position(), "Reference to unavailable annotation class "
+                                        + t.fullName());
+                            }
+                        }
+                    }
+                }
+            } else if (cl.isDeprecated()) {
+                // not hidden, but deprecated
+                Errors.error(Errors.DEPRECATED,
+                        cl.position(), "Class " + cl.qualifiedName()
+                        + " is deprecated");
+            }
+        }
+
+        // write out the stubs
+        HashMap<PackageInfo, List<ClassInfo>> packages = new HashMap<PackageInfo, List<ClassInfo>>();
+        for (ClassInfo cl: notStrippable) {
+            if (!cl.isDocOnly()) {
+                if (stubPackages == null || stubPackages.contains(cl.containingPackage().name())) {
+                    writeClassFile(stubsDir, cl);
+                    if (packages.containsKey(cl.containingPackage())) {
+                        packages.get(cl.containingPackage()).add(cl);
+                    } else {
+                        ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
+                        classes.add(cl);
+                        packages.put(cl.containingPackage(), classes);
+                    }
+                }
+            }
+        }
+
+        // write out the XML
+        if (writeXML && xmlWriter != null) {
+            writeXML(xmlWriter, packages, notStrippable);
+        }
+
+        if (xmlWriter != null) {
+            xmlWriter.close();
+        }
+
+    }
+
+    public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
+
+      if (!notStrippable.add(cl)) {
+        // slight optimization: if it already contains cl, it already contains
+        // all of cl's parents
+          return;
+      }
+      cl.setReasonIncluded(why);
+
+      // cant strip annotations
+      /*if (cl.annotations() != null){
+          for (AnnotationInstanceInfo ai : cl.annotations()){
+              if (ai.type() != null){
+                  cantStripThis(ai.type(), notStrippable, "1:" + cl.qualifiedName());
+              }
+          }
+      }*/
+      // cant strip any public fields or their generics
+      if (cl.allSelfFields() != null){
+          for (FieldInfo fInfo : cl.allSelfFields()){
+              if (fInfo.type() != null){
+                  if (fInfo.type().asClassInfo() != null){
+                      cantStripThis(fInfo.type().asClassInfo(), notStrippable,
+                          "2:" + cl.qualifiedName());
+                  }
+                  if (fInfo.type().typeArguments() != null){
+                      for (TypeInfo tTypeInfo : fInfo.type().typeArguments()){
+                          if (tTypeInfo.asClassInfo() != null){
+                              cantStripThis(tTypeInfo.asClassInfo(), notStrippable,
+                                  "3:" + cl.qualifiedName());
+                          }
+                      }
+                  }
+              }
+          }
+      }
+      //cant strip any of the type's generics
+      if (cl.asTypeInfo() != null){
+          if (cl.asTypeInfo().typeArguments() != null){
+              for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()){
+                  if (tInfo.asClassInfo() != null){
+                      cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName());
+                  }
+              }
+          }
+      }
+      //cant strip any of the annotation elements
+      //cantStripThis(cl.annotationElements(), notStrippable);
+      // take care of methods
+      cantStripThis(cl.allSelfMethods(), notStrippable);
+      cantStripThis(cl.allConstructors(), notStrippable);
+      // blow the outer class open if this is an inner class
+      if(cl.containingClass() != null){
+          cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName());
+      }
+      // blow open super class and interfaces
+     ClassInfo supr = cl.realSuperclass();
+      if (supr != null) {
+          if (supr.isHidden()) {
+              // cl is a public class declared as extending a hidden superclass.
+              // this is not a desired practice but it's happened, so we deal
+              // with it by stripping off the superclass relation for purposes of
+              // generating the doc & stub information, and proceeding normally.
+              cl.init(cl.asTypeInfo(), cl.realInterfaces(), cl.realInterfaceTypes(),
+                      cl.innerClasses(), cl.allConstructors(), cl.allSelfMethods(),
+                      cl.annotationElements(), cl.allSelfFields(), cl.enumConstants(),
+                      cl.containingPackage(), cl.containingClass(),
+                      null, null, cl.annotations());
+              Errors.error(Errors.HIDDEN_SUPERCLASS,
+                      cl.position(), "Public class " + cl.qualifiedName()
+                      + " stripped of unavailable superclass "
+                      + supr.qualifiedName());
+          } else {
+              cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name()
+                      + cl.qualifiedName());
+          }
+      }
+    }
+
+    private static void cantStripThis(MethodInfo[] mInfos , HashSet<ClassInfo> notStrippable) {
+      //for each method, blow open the parameters, throws and return types.  also blow open their generics
+      if (mInfos != null){
+          for (MethodInfo mInfo : mInfos){
+              if (mInfo.getTypeParameters() != null){
+                  for (TypeInfo tInfo : mInfo.getTypeParameters()){
+                      if (tInfo.asClassInfo() != null){
+                          cantStripThis(tInfo.asClassInfo(), notStrippable, "8:" +
+                                        mInfo.realContainingClass().qualifiedName() + ":" +
+                                        mInfo.name());
+                      }
+                  }
+              }
+              if (mInfo.parameters() != null){
+                  for (ParameterInfo pInfo : mInfo.parameters()){
+                      if (pInfo.type() != null && pInfo.type().asClassInfo() != null){
+                          cantStripThis(pInfo.type().asClassInfo(), notStrippable,
+                                        "9:"+  mInfo.realContainingClass().qualifiedName()
+                                        + ":" + mInfo.name());
+                          if (pInfo.type().typeArguments() != null){
+                              for (TypeInfo tInfoType : pInfo.type().typeArguments()){
+                                  if (tInfoType.asClassInfo() != null){
+                                      ClassInfo tcl = tInfoType.asClassInfo();
+                                      if (tcl.isHidden()) {
+                                          Errors.error(Errors.UNAVAILABLE_SYMBOL, mInfo.position(),
+                                                  "Parameter of hidden type "
+                                                  + tInfoType.fullName() + " in "
+                                                  + mInfo.containingClass().qualifiedName()
+                                                  + '.' + mInfo.name() + "()");
+                                      } else {
+                                          cantStripThis(tcl, notStrippable,
+                                                  "10:" +
+                                                  mInfo.realContainingClass().qualifiedName() + ":" +
+                                                  mInfo.name());
+                                      }
+                                  }
+                              }
+                          }
+                      }
+                  }
+              }
+              for (ClassInfo thrown : mInfo.thrownExceptions()){
+                  cantStripThis(thrown, notStrippable, "11:" +
+                                mInfo.realContainingClass().qualifiedName()
+                                +":" + mInfo.name());
+              }
+              if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null){
+                  cantStripThis(mInfo.returnType().asClassInfo(), notStrippable,
+                                "12:" + mInfo.realContainingClass().qualifiedName() +
+                                ":" + mInfo.name());
+                  if (mInfo.returnType().typeArguments() != null){
+                      for (TypeInfo tyInfo: mInfo.returnType().typeArguments() ){
+                          if (tyInfo.asClassInfo() != null){
+                              cantStripThis(tyInfo.asClassInfo(), notStrippable,
+                                            "13:" +
+                                            mInfo.realContainingClass().qualifiedName()
+                                            + ":" + mInfo.name());
+                          }
+                      }
+                  }
+              }
+          }
+      }
+    }
+
+    static String javaFileName(ClassInfo cl) {
+        String dir = "";
+        PackageInfo pkg = cl.containingPackage();
+        if (pkg != null) {
+            dir = pkg.name();
+            dir = dir.replace('.', '/') + '/';
+        }
+        return dir + cl.name() + ".java";
+    }
+
+    static void writeClassFile(String stubsDir, ClassInfo cl) {
+        // inner classes are written by their containing class
+        if (cl.containingClass() != null) {
+            return;
+        }
+
+        String filename = stubsDir + '/' + javaFileName(cl);
+        File file = new File(filename);
+        ClearPage.ensureDirectory(file);
+
+        PrintStream stream = null;
+        try {
+            stream = new PrintStream(file);
+            writeClassFile(stream, cl);
+        }
+        catch (FileNotFoundException e) {
+            System.err.println("error writing file: " + filename);
+        }
+        finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+    }
+
+    static void writeClassFile(PrintStream stream, ClassInfo cl) {
+        PackageInfo pkg = cl.containingPackage();
+        if (pkg != null) {
+            stream.println("package " + pkg.name() + ";");
+        }
+        writeClass(stream, cl);
+    }
+
+    static void writeClass(PrintStream stream, ClassInfo cl) {
+        writeAnnotations(stream, cl.annotations());
+
+        stream.print(DroidDoc.scope(cl) + " ");
+        if (cl.isAbstract() && !cl.isAnnotation() && !cl.isInterface()) {
+            stream.print("abstract ");
+        }
+        if (cl.isStatic()){
+            stream.print("static ");
+        }
+        if (cl.isFinal() && !cl.isEnum()) {
+            stream.print("final ");
+        }
+        if (false) {
+            stream.print("strictfp ");
+        }
+
+        HashSet<String> classDeclTypeVars = new HashSet();
+        String leafName = cl.asTypeInfo().fullName(classDeclTypeVars);
+        int bracket = leafName.indexOf('<');
+        if (bracket < 0) bracket = leafName.length() - 1;
+        int period = leafName.lastIndexOf('.', bracket);
+        if (period < 0) period = -1;
+        leafName = leafName.substring(period+1);
+
+        String kind = cl.kind();
+        stream.println(kind + " " + leafName);
+
+        TypeInfo base = cl.superclassType();
+
+        if (!"enum".equals(kind)) {
+            if (base != null && !"java.lang.Object".equals(base.qualifiedTypeName())) {
+                stream.println("  extends " + base.fullName(classDeclTypeVars));
+            }
+        }
+
+        TypeInfo[] interfaces = cl.realInterfaceTypes();
+        List<TypeInfo> usedInterfaces = new ArrayList<TypeInfo>();
+        for (TypeInfo iface : interfaces) {
+            if (notStrippable.contains(iface.asClassInfo())
+                    && !iface.asClassInfo().isDocOnly()) {
+                usedInterfaces.add(iface);
+            }
+        }
+        if (usedInterfaces.size() > 0 && !cl.isAnnotation()) {
+            // can java annotations extend other ones?
+            if (cl.isInterface() || cl.isAnnotation()) {
+                stream.print("  extends ");
+            } else {
+                stream.print("  implements ");
+            }
+            String comma = "";
+            for (TypeInfo iface: usedInterfaces) {
+                stream.print(comma + iface.fullName(classDeclTypeVars));
+                comma = ", ";
+            }
+            stream.println();
+        }
+
+        stream.println("{");
+
+        FieldInfo[] enumConstants = cl.enumConstants();
+        int N = enumConstants.length;
+        for (int i=0; i<N; i++) {
+            FieldInfo field = enumConstants[i];
+            if (!field.constantLiteralValue().equals("null")){
+            stream.println(field.name() + "(" + field.constantLiteralValue()
+                    + (i==N-1 ? ");" : "),"));
+            }else{
+              stream.println(field.name() + "(" + (i==N-1 ? ");" : "),"));
+            }
+        }
+
+        for (ClassInfo inner: cl.getRealInnerClasses()) {
+            if (notStrippable.contains(inner)
+                    && !inner.isDocOnly()){
+                writeClass(stream, inner);
+            }
+        }
+
+
+        for (MethodInfo method: cl.constructors()) {
+            if (!method.isDocOnly()) {
+                writeMethod(stream, method, true);
+            }
+        }
+
+        boolean fieldNeedsInitialization = false;
+        boolean staticFieldNeedsInitialization = false;
+        for (FieldInfo field: cl.allSelfFields()) {
+            if (!field.isDocOnly()) {
+                if (!field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
+                    fieldNeedsInitialization = true;
+                }
+                if (field.isStatic() && field.isFinal() && !fieldIsInitialized(field)) {
+                    staticFieldNeedsInitialization = true;
+                }
+            }
+        }
+
+        // The compiler includes a default public constructor that calls the super classes
+        // default constructor in the case where there are no written constructors.
+        // So, if we hide all the constructors, java may put in a constructor
+        // that calls a nonexistent super class constructor.  So, if there are no constructors,
+        // and the super class doesn't have a default constructor, write in a private constructor
+        // that works.  TODO -- we generate this as protected, but we really should generate
+        // it as private unless it also exists in the real code.
+        if ((cl.constructors().length == 0 && (cl.getNonWrittenConstructors().length != 0
+                    || fieldNeedsInitialization))
+                && !cl.isAnnotation()
+                && !cl.isInterface()
+                && !cl.isEnum() ) {
+            //Errors.error(Errors.HIDDEN_CONSTRUCTOR,
+            //             cl.position(), "No constructors " +
+            //            "found and superclass has no parameterless constructor.  A constructor " +
+            //            "that calls an appropriate superclass constructor " +
+            //            "was automatically written to stubs.\n");
+            stream.println(cl.leafName()
+                    + "() { " + superCtorCall(cl,null)
+                    + "throw new" + " RuntimeException(\"Stub!\"); }");
+        }
+
+        for (MethodInfo method: cl.allSelfMethods()) {
+            if (cl.isEnum()) {
+                if (("values".equals(method.name())
+                            && "()".equals(method.signature()))
+                    || ("valueOf".equals(method.name())
+                            && "(java.lang.String)".equals(method.signature()))) {
+                    // skip these two methods on enums, because they're synthetic,
+                    // although for some reason javadoc doesn't mark them as synthetic,
+                    // maybe because they still want them documented
+                    continue;
+                }
+            }
+            if (!method.isDocOnly()) {
+                writeMethod(stream, method, false);
+            }
+        }
+        //Write all methods that are hidden, but override abstract methods or interface methods.
+        //These can't be hidden.
+        for (MethodInfo method : cl.getHiddenMethods()){
+            MethodInfo overriddenMethod = method.findRealOverriddenMethod(method.name(), method.signature(), notStrippable);
+            ClassInfo classContainingMethod = method.findRealOverriddenClass(method.name(),
+                                                                             method.signature());
+            if (overriddenMethod != null && !overriddenMethod.isHidden()
+                && !overriddenMethod.isDocOnly() &&
+                (overriddenMethod.isAbstract() ||
+                overriddenMethod.containingClass().isInterface())) {
+                method.setReason("1:" + classContainingMethod.qualifiedName());
+                cl.addMethod(method);
+                writeMethod(stream, method, false);
+            }
+        }
+
+        for (MethodInfo element: cl.annotationElements()) {
+            if (!element.isDocOnly()) {
+                writeAnnotationElement(stream, element);
+            }
+        }
+
+        for (FieldInfo field: cl.allSelfFields()) {
+            if (!field.isDocOnly()) {
+                writeField(stream, field);
+            }
+        }
+
+        if (staticFieldNeedsInitialization) {
+            stream.print("static { ");
+            for (FieldInfo field: cl.allSelfFields()) {
+                if (!field.isDocOnly() && field.isStatic() && field.isFinal()
+                        && !fieldIsInitialized(field) && field.constantValue() == null) {
+                    stream.print(field.name() + " = " + field.type().defaultValue()
+                            + "; ");
+                }
+            }
+            stream.println("}");
+        }
+
+        stream.println("}");
+    }
+
+
+    static void writeMethod(PrintStream stream, MethodInfo method, boolean isConstructor) {
+        String comma;
+
+        stream.print(DroidDoc.scope(method) + " ");
+        if (method.isStatic()) {
+            stream.print("static ");
+        }
+        if (method.isFinal()) {
+            stream.print("final ");
+        }
+        if (method.isAbstract()) {
+            stream.print("abstract ");
+        }
+        if (method.isSynchronized()) {
+            stream.print("synchronized ");
+        }
+        if (method.isNative()) {
+            stream.print("native ");
+        }
+        if (false /*method.isStictFP()*/) {
+            stream.print("strictfp ");
+        }
+
+        stream.print(method.typeArgumentsName(new HashSet()) + " ");
+
+        if (!isConstructor) {
+            stream.print(method.returnType().fullName(method.typeVariables()) + " ");
+        }
+        String n = method.name();
+        int pos = n.lastIndexOf('.');
+        if (pos >= 0) {
+            n = n.substring(pos + 1);
+        }
+        stream.print(n + "(");
+        comma = "";
+        int count = 1;
+        int size = method.parameters().length;
+        for (ParameterInfo param: method.parameters()) {
+            stream.print(comma + fullParameterTypeName(method, param.type(), count == size)
+                    + " " + param.name());
+            comma = ", ";
+            count++;
+        }
+        stream.print(")");
+
+        comma = "";
+        if (method.thrownExceptions().length > 0) {
+            stream.print(" throws ");
+            for (ClassInfo thrown: method.thrownExceptions()) {
+                stream.print(comma + thrown.qualifiedName());
+                comma = ", ";
+            }
+        }
+        if (method.isAbstract() || method.isNative() || method.containingClass().isInterface()) {
+            stream.println(";");
+        } else {
+            stream.print(" { ");
+            if (isConstructor) {
+                stream.print(superCtorCall(method.containingClass(), method.thrownExceptions()));
+            }
+            stream.println("throw new RuntimeException(\"Stub!\"); }");
+        }
+    }
+
+    static void writeField(PrintStream stream, FieldInfo field) {
+        stream.print(DroidDoc.scope(field) + " ");
+        if (field.isStatic()) {
+            stream.print("static ");
+        }
+        if (field.isFinal()) {
+            stream.print("final ");
+        }
+        if (field.isTransient()) {
+            stream.print("transient ");
+        }
+        if (field.isVolatile()) {
+            stream.print("volatile ");
+        }
+
+        stream.print(field.type().fullName());
+        stream.print(" ");
+        stream.print(field.name());
+
+        if (fieldIsInitialized(field)) {
+            stream.print(" = " + field.constantLiteralValue());
+        }
+
+        stream.println(";");
+    }
+
+    static boolean fieldIsInitialized(FieldInfo field) {
+        return (field.isFinal() && field.constantValue() != null)
+                || !field.type().dimension().equals("")
+                || field.containingClass().isInterface();
+    }
+
+    // Returns 'true' if the method is an @Override of a visible parent
+    // method implementation, and thus does not affect the API.
+    static boolean methodIsOverride(MethodInfo mi) {
+        // Abstract/static/final methods are always listed in the API description
+        if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
+            return false;
+        }
+
+        // Find any relevant ancestor declaration and inspect it
+        MethodInfo om = mi.findSuperclassImplementation(notStrippable);
+        if (om != null) {
+            // Visibility mismatch is an API change, so check for it
+            if (mi.mIsPrivate == om.mIsPrivate
+                    && mi.mIsPublic == om.mIsPublic
+                    && mi.mIsProtected == om.mIsProtected) {
+                // Look only for overrides of an ancestor class implementation,
+                // not of e.g. an abstract or interface method declaration
+                if (!om.isAbstract()) {
+                    // If the parent is hidden, we can't rely on it to provide
+                    // the API
+                    if (!om.isHidden()) {
+                        // If the only "override" turns out to be in our own class
+                        // (which sometimes happens in concrete subclasses of
+                        // abstract base classes), it's not really an override
+                        if (!mi.mContainingClass.equals(om.mContainingClass)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    static boolean canCallMethod(ClassInfo from, MethodInfo m) {
+        if (m.isPublic() || m.isProtected()) {
+            return true;
+        }
+        if (m.isPackagePrivate()) {
+            String fromPkg = from.containingPackage().name();
+            String pkg = m.containingClass().containingPackage().name();
+            if (fromPkg.equals(pkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // call a constructor, any constructor on this class's superclass.
+    static String superCtorCall(ClassInfo cl, ClassInfo[] thrownExceptions) {
+        ClassInfo base = cl.realSuperclass();
+        if (base == null) {
+            return "";
+        }
+        HashSet<String> exceptionNames = new HashSet<String>();
+        if (thrownExceptions != null ){
+            for (ClassInfo thrown : thrownExceptions){
+              exceptionNames.add(thrown.name());
+            }
+        }
+        MethodInfo[] ctors = base.constructors();
+        MethodInfo ctor = null;
+        //bad exception indicates that the exceptions thrown by the super constructor
+        //are incompatible with the constructor we're using for the sub class.
+        Boolean badException = false;
+        for (MethodInfo m: ctors) {
+            if (canCallMethod(cl, m)) {
+                if (m.thrownExceptions() != null){
+                    for (ClassInfo thrown : m.thrownExceptions()){
+                        if (!exceptionNames.contains(thrown.name())){
+                            badException = true;
+                        }
+                    }
+                }
+                if (badException){
+                  badException = false;
+                  continue;
+                }
+                // if it has no args, we're done
+                if (m.parameters().length == 0) {
+                    return "";
+                }
+                ctor = m;
+            }
+        }
+        if (ctor != null) {
+            String result = "";
+            result+= "super(";
+            ParameterInfo[] params = ctor.parameters();
+            int N = params.length;
+            for (int i=0; i<N; i++) {
+                TypeInfo t = params[i].type();
+                if (t.isPrimitive() && t.dimension().equals("")) {
+                    String n = t.simpleTypeName();
+                    if (("byte".equals(n)
+                            || "short".equals(n)
+                            || "int".equals(n)
+                            || "long".equals(n)
+                            || "float".equals(n)
+                            || "double".equals(n)) && t.dimension().equals("")) {
+                        result += "0";
+                    }
+                    else if ("char".equals(n)) {
+                        result += "'\\0'";
+                    }
+                    else if ("boolean".equals(n)) {
+                        result += "false";
+                    }
+                    else {
+                        result += "<<unknown-" + n + ">>";
+                    }
+                } else {
+                    //put null in each super class method.  Cast null to the correct type
+                    //to avoid collisions with other constructors.  If the type is generic
+                    //don't cast it
+                    result += (!t.isTypeVariable() ? "(" + t.qualifiedTypeName() + t.dimension() +
+                              ")" : "") + "null";
+                }
+                if (i != N-1) {
+                    result += ",";
+                }
+            }
+            result += "); ";
+            return result;
+        } else {
+            return "";
+        }
+    }
+
+    static void writeAnnotations(PrintStream stream, AnnotationInstanceInfo[] annotations) {
+        for (AnnotationInstanceInfo ann: annotations) {
+            if (!ann.type().isHidden()) {
+                stream.println(ann.toString());
+            }
+        }
+    }
+
+    static void writeAnnotationElement(PrintStream stream, MethodInfo ann) {
+        stream.print(ann.returnType().fullName());
+        stream.print(" ");
+        stream.print(ann.name());
+        stream.print("()");
+        AnnotationValueInfo def = ann.defaultAnnotationElementValue();
+        if (def != null) {
+            stream.print(" default ");
+            stream.print(def.valueString());
+        }
+        stream.println(";");
+    }
+
+    static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
+                         HashSet notStrippable) {
+        // extract the set of packages, sort them by name, and write them out in that order
+        Set<PackageInfo> allClassKeys = allClasses.keySet();
+        PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
+        Arrays.sort(allPackages, PackageInfo.comparator);
+
+        xmlWriter.println("<api>");
+        for (PackageInfo pack : allPackages) {
+            writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable);
+        }
+        xmlWriter.println("</api>");
+    }
+
+    static void writePackageXML(PrintStream xmlWriter, PackageInfo pack, List<ClassInfo> classList,
+                                HashSet notStrippable) {
+        ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
+        Arrays.sort(classes, ClassInfo.comparator);
+        xmlWriter.println("<package name=\"" + pack.name() + "\"\n"
+                //+ " source=\"" + pack.position() + "\"\n"
+                + ">");
+        for (ClassInfo cl : classes) {
+            writeClassXML(xmlWriter, cl, notStrippable);
+        }
+        xmlWriter.println("</package>");
+
+
+    }
+
+    static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet notStrippable) {
+        String scope = DroidDoc.scope(cl);
+        String deprecatedString = "";
+        String declString = (cl.isInterface()) ? "interface" : "class";
+        if (cl.isDeprecated()) {
+            deprecatedString = "deprecated";
+        } else {
+            deprecatedString = "not deprecated";
+        }
+        xmlWriter.println("<" + declString + " name=\"" + cl.name() + "\"");
+        if (!cl.isInterface() && !cl.qualifiedName().equals("java.lang.Object")) {
+            xmlWriter.println(" extends=\"" + ((cl.realSuperclass() == null)
+                            ? "java.lang.Object"
+                            : cl.realSuperclass().qualifiedName()) + "\"");
+        }
+        xmlWriter.println(" abstract=\"" + cl.isAbstract() + "\"\n"
+                + " static=\"" + cl.isStatic() + "\"\n"
+                + " final=\"" + cl.isFinal() + "\"\n"
+                + " deprecated=\"" + deprecatedString + "\"\n"
+                + " visibility=\"" + scope + "\"\n"
+                //+ " source=\"" + cl.position() + "\"\n"
+                + ">");
+
+        ClassInfo[] interfaces = cl.realInterfaces();
+        Arrays.sort(interfaces, ClassInfo.comparator);
+        for (ClassInfo iface : interfaces) {
+            if (notStrippable.contains(iface)) {
+                xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">");
+                xmlWriter.println("</implements>");
+            }
+        }
+
+        MethodInfo[] constructors = cl.constructors();
+        Arrays.sort(constructors, MethodInfo.comparator);
+        for (MethodInfo mi : constructors) {
+            writeConstructorXML(xmlWriter, mi);
+        }
+
+        MethodInfo[] methods = cl.allSelfMethods();
+        Arrays.sort(methods, MethodInfo.comparator);
+        for (MethodInfo mi : methods) {
+            if (!methodIsOverride(mi)) {
+                writeMethodXML(xmlWriter, mi);
+            }
+        }
+
+        FieldInfo[] fields = cl.allSelfFields();
+        Arrays.sort(fields, FieldInfo.comparator);
+        for (FieldInfo fi : fields) {
+            writeFieldXML(xmlWriter, fi);
+        }
+        xmlWriter.println("</" + declString + ">");
+
+    }
+
+    static void writeMethodXML(PrintStream xmlWriter, MethodInfo mi) {
+        String scope = DroidDoc.scope(mi);
+
+        String deprecatedString = "";
+        if (mi.isDeprecated()) {
+            deprecatedString = "deprecated";
+        } else {
+            deprecatedString = "not deprecated";
+        }
+        xmlWriter.println("<method name=\"" + mi.name() + "\"\n"
+                + ((mi.returnType() != null)
+                        ? " return=\"" + makeXMLcompliant(fullParameterTypeName(mi, mi.returnType(), false)) + "\"\n"
+                        : "")
+                + " abstract=\"" + mi.isAbstract() + "\"\n"
+                + " native=\"" + mi.isNative() + "\"\n"
+                + " synchronized=\"" + mi.isSynchronized() + "\"\n"
+                + " static=\"" + mi.isStatic() + "\"\n"
+                + " final=\"" + mi.isFinal() + "\"\n"
+                + " deprecated=\""+ deprecatedString + "\"\n"
+                + " visibility=\"" + scope + "\"\n"
+                //+ " source=\"" + mi.position() + "\"\n"
+                + ">");
+
+        // write parameters in declaration order
+        int numParameters = mi.parameters().length;
+        int count = 0;
+        for (ParameterInfo pi : mi.parameters()) {
+            count++;
+            writeParameterXML(xmlWriter, mi, pi, count == numParameters);
+        }
+
+        // but write exceptions in canonicalized order
+        ClassInfo[] exceptions = mi.thrownExceptions();
+        Arrays.sort(exceptions, ClassInfo.comparator);
+        for (ClassInfo pi : exceptions) {
+          xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName()
+                            + "\">");
+          xmlWriter.println("</exception>");
+        }
+        xmlWriter.println("</method>");
+    }
+
+    static void writeConstructorXML(PrintStream xmlWriter, MethodInfo mi) {
+        String scope = DroidDoc.scope(mi);
+        String deprecatedString = "";
+        if (mi.isDeprecated()) {
+            deprecatedString = "deprecated";
+        } else {
+            deprecatedString = "not deprecated";
+        }
+        xmlWriter.println("<constructor name=\"" + mi.name() + "\"\n"
+                + " type=\"" + mi.containingClass().qualifiedName() + "\"\n"
+                + " static=\"" + mi.isStatic() + "\"\n"
+                + " final=\"" + mi.isFinal() + "\"\n"
+                + " deprecated=\"" + deprecatedString + "\"\n"
+                + " visibility=\"" + scope +"\"\n"
+                //+ " source=\"" + mi.position() + "\"\n"
+                + ">");
+
+        int numParameters = mi.parameters().length;
+        int count = 0;
+        for (ParameterInfo pi : mi.parameters()) {
+            count++;
+            writeParameterXML(xmlWriter, mi, pi, count == numParameters);
+        }
+
+        ClassInfo[] exceptions = mi.thrownExceptions();
+        Arrays.sort(exceptions, ClassInfo.comparator);
+        for (ClassInfo pi : exceptions) {
+            xmlWriter.println("<exception name=\"" + pi.name() +"\" type=\"" + pi.qualifiedName()
+                              + "\">");
+            xmlWriter.println("</exception>");
+        }
+        xmlWriter.println("</constructor>");
+  }
+
+    static void writeParameterXML(PrintStream xmlWriter, MethodInfo method,
+            ParameterInfo pi, boolean isLast) {
+        xmlWriter.println("<parameter name=\"" + pi.name() + "\" type=\"" +
+                makeXMLcompliant(fullParameterTypeName(method, pi.type(), isLast)) + "\">");
+        xmlWriter.println("</parameter>");
+    }
+
+    static void writeFieldXML(PrintStream xmlWriter, FieldInfo fi) {
+        String scope = DroidDoc.scope(fi);
+        String deprecatedString = "";
+        if (fi.isDeprecated()) {
+            deprecatedString = "deprecated";
+        } else {
+            deprecatedString = "not deprecated";
+        }
+        //need to make sure value is valid XML
+        String value  = makeXMLcompliant(fi.constantLiteralValue());
+
+        String fullTypeName = makeXMLcompliant(fi.type().qualifiedTypeName())
+                + fi.type().dimension();
+
+        xmlWriter.println("<field name=\"" + fi.name() +"\"\n"
+                          + " type=\"" + fullTypeName + "\"\n"
+                          + " transient=\"" + fi.isTransient() + "\"\n"
+                          + " volatile=\"" + fi.isVolatile() + "\"\n"
+                          + (fieldIsInitialized(fi) ? " value=\"" + value + "\"\n" : "")
+                          + " static=\"" + fi.isStatic() + "\"\n"
+                          + " final=\"" + fi.isFinal() + "\"\n"
+                          + " deprecated=\"" + deprecatedString + "\"\n"
+                          + " visibility=\"" + scope + "\"\n"
+                          //+ " source=\"" + fi.position() + "\"\n"
+                          + ">");
+        xmlWriter.println("</field>");
+    }
+
+    static String makeXMLcompliant(String s) {
+        String returnString = "";
+        returnString = s.replaceAll("&", "&amp;");
+        returnString = returnString.replaceAll("<", "&lt;");
+        returnString = returnString.replaceAll(">", "&gt;");
+        returnString = returnString.replaceAll("\"", "&quot;");
+        returnString = returnString.replaceAll("'", "&pos;");
+        return returnString;
+    }
+
+    static String fullParameterTypeName(MethodInfo method, TypeInfo type, boolean isLast) {
+        String fullTypeName = type.fullName(method.typeVariables());
+        if (isLast && method.isVarArgs()) {
+            // TODO: note that this does not attempt to handle hypothetical
+            // vararg methods whose last parameter is a list of arrays, e.g.
+            // "Object[]...".
+            fullTypeName = type.fullNameNoDimension(method.typeVariables()) + "...";
+        }
+        return fullTypeName;
+    }
+}
diff --git a/tools/droiddoc/src/TagInfo.java b/tools/droiddoc/src/TagInfo.java
new file mode 100644
index 0000000..d25c500
--- /dev/null
+++ b/tools/droiddoc/src/TagInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+
+public class TagInfo
+{
+    private String mName;
+    private String mText;
+    private String mKind;
+    private SourcePositionInfo mPosition;
+
+    TagInfo(String n, String k, String t, SourcePositionInfo sp)
+    {
+        mName = n;
+        mText = t;
+        mKind = k;
+        mPosition = sp;
+    }
+
+    String name()
+    {
+        return mName;
+    }
+
+    String text()
+    {
+        return mText;
+    }
+
+    String kind()
+    {
+        return mKind;
+    }
+    
+    SourcePositionInfo position() {
+        return mPosition;
+    }
+
+    void setKind(String kind) {
+        mKind = kind;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        data.setValue(base + ".name", name());
+        data.setValue(base + ".text", text());
+        data.setValue(base + ".kind", kind());
+    }
+
+    public static void makeHDF(HDF data, String base, TagInfo[] tags)
+    {
+        makeHDF(data, base, tags, null, 0, 0);
+    }
+
+    public static void makeHDF(HDF data, String base, InheritedTags tags)
+    {
+        makeHDF(data, base, tags.tags(), tags.inherited(), 0, 0);
+    }
+
+    private static int makeHDF(HDF data, String base, TagInfo[] tags,
+                                InheritedTags inherited, int j, int depth)
+    {
+        int i;
+        int len = tags.length;
+        if (len == 0 && inherited != null) {
+            j = makeHDF(data, base, inherited.tags(), inherited.inherited(), j, depth+1);
+        } else {
+            for (i=0; i<len; i++, j++) {
+                TagInfo t = tags[i];
+                if (inherited != null && t.name().equals("@inheritDoc")) {
+                    j = makeHDF(data, base, inherited.tags(),
+                                    inherited.inherited(), j, depth+1);
+                } else {
+                    if (t.name().equals("@inheritDoc")) {
+                        Errors.error(Errors.BAD_INHERITDOC, t.mPosition,
+                                "@inheritDoc on class/method that is not inherited");
+                    }
+                    t.makeHDF(data, base + "." + j);
+                }
+            }
+        }
+        return j;
+    }
+}
+
diff --git a/tools/droiddoc/src/TextTagInfo.java b/tools/droiddoc/src/TextTagInfo.java
new file mode 100644
index 0000000..dcdfdd9
--- /dev/null
+++ b/tools/droiddoc/src/TextTagInfo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class TextTagInfo extends TagInfo {
+    TextTagInfo(String n, String k, String t, SourcePositionInfo p) {
+        super(n, k, DroidDoc.escape(t), p);
+    }
+}
diff --git a/tools/droiddoc/src/ThrowsTagInfo.java b/tools/droiddoc/src/ThrowsTagInfo.java
new file mode 100644
index 0000000..318a57d
--- /dev/null
+++ b/tools/droiddoc/src/ThrowsTagInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class ThrowsTagInfo extends ParsedTagInfo
+{
+    static final Pattern PATTERN = Pattern.compile(
+                                "(\\S+)\\s+(.*)",
+                                Pattern.DOTALL);
+    private ClassInfo mException;
+
+    public ThrowsTagInfo(String name, String kind, String text,
+            ContainerInfo base, SourcePositionInfo sp)
+    {
+        super(name, kind, text, base, sp);
+
+        Matcher m = PATTERN.matcher(text);
+        if (m.matches()) {
+            setCommentText(m.group(2));
+            String className = m.group(1);
+            if (base instanceof ClassInfo) {
+                mException = ((ClassInfo)base).findClass(className);
+            }
+            if (mException == null) {
+                mException = Converter.obtainClass(className);
+            }
+        }
+    }
+
+    public ThrowsTagInfo(String name, String kind, String text,
+                            ClassInfo exception, String exceptionComment,
+                            ContainerInfo base, SourcePositionInfo sp)
+    {
+        super(name, kind, text, base, sp);
+        mException = exception;
+        setCommentText(exceptionComment);
+    }
+
+    public ClassInfo exception()
+    {
+        return mException;
+    }
+
+    public TypeInfo exceptionType()
+    {
+        if (mException != null) {
+            return mException.asTypeInfo();
+        } else {
+            return null;
+        }
+    }
+
+    public static void makeHDF(HDF data, String base, ThrowsTagInfo[] tags)
+    {
+        for (int i=0; i<tags.length; i++) {
+            TagInfo.makeHDF(data, base + '.' + i + ".comment",
+                    tags[i].commentTags());
+            if (tags[i].exceptionType() != null) {
+                tags[i].exceptionType().makeHDF(data, base + "." + i + ".type");
+            }
+        }
+    }
+
+    
+}
+
diff --git a/tools/droiddoc/src/TodoFile.java b/tools/droiddoc/src/TodoFile.java
new file mode 100644
index 0000000..ebef27e
--- /dev/null
+++ b/tools/droiddoc/src/TodoFile.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public class TodoFile {
+
+    public static final String MISSING = "No description text";
+
+    public static boolean areTagsUseful(InheritedTags tags) {
+        while (tags != null) {
+            if (areTagsUseful(tags.tags())) {
+                return true;
+            }
+            tags = tags.inherited();
+        }
+        return false;
+    }
+
+    public static boolean areTagsUseful(TagInfo[] tags) {
+        for (TagInfo t: tags) {
+            if ("Text".equals(t.name()) && t.text().trim().length() != 0) {
+                return true;
+            }
+            if ("@inheritDoc".equals(t.name())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void setHDF(HDF data, String base, SourcePositionInfo pos, String name,
+            String descr) {
+        data.setValue(base + ".pos", pos.toString());
+        data.setValue(base + ".name", name);
+        data.setValue(base + ".descr", descr);
+    }
+
+    static class PackageStats {
+        String name;
+        public int total;
+        public int errors;
+    }
+
+    public static String percent(int a, int b) {
+        return ""+Math.round((((b-a)/(float)b))*100) + "%";
+    }
+
+    public static void writeTodoFile(String filename) {
+        HDF data = DroidDoc.makeHDF();
+        DroidDoc.setPageTitle(data, "Missing Documentation");
+        TreeMap<String,PackageStats> packageStats = new TreeMap<String,PackageStats>();
+
+        ClassInfo[] classes = Converter.rootClasses();
+        Arrays.sort(classes);
+
+        int classIndex = 0;
+        
+        for (ClassInfo cl: classes) {
+            if (cl.isHidden()) {
+                continue;
+            }
+
+            String classBase = "classes." + classIndex;
+
+            String base = classBase + ".errors.";
+            int errors = 0;
+            int total = 1;
+
+            if (!areTagsUseful(cl.inlineTags())) {
+                setHDF(data, base + errors, cl.position(), "&lt;class comment&gt;", MISSING);
+                errors++;
+            }
+
+
+            for (MethodInfo m: cl.constructors()) {
+                boolean good = true;
+                total++;
+                if (m.checkLevel()) {
+                    if (!areTagsUseful(m.inlineTags())) {
+                        setHDF(data, base + errors, m.position(), m.name() + m.prettySignature(),
+                                MISSING);
+                        good = false;
+                    }
+                }
+                if (!good) {
+                    errors++;
+                }
+            }
+            
+            for (MethodInfo m: cl.selfMethods()) {
+                boolean good = true;
+                total++;
+                if (m.checkLevel()) {
+                    if (!areTagsUseful(m.inlineTags())) {
+                        setHDF(data, base + errors, m.position(), m.name() + m.prettySignature(),
+                                MISSING);
+                        good = false;
+                    }
+                }
+                if (!good) {
+                    errors++;
+                }
+            }
+            
+
+            for (FieldInfo f: cl.enumConstants()) {
+                boolean good = true;
+                total++;
+                if (f.checkLevel()) {
+                    if (!areTagsUseful(f.inlineTags())) {
+                        setHDF(data, base + errors, f.position(), f.name(), MISSING);
+                        good = false;
+                    }
+                }
+                if (!good) {
+                    errors++;
+                }
+            }
+            
+            for (FieldInfo f: cl.selfFields()) {
+                boolean good = true;
+                total++;
+                if (f.checkLevel()) {
+                    if (!areTagsUseful(f.inlineTags())) {
+                        setHDF(data, base + errors, f.position(), f.name(), MISSING);
+                        good = false;
+                    }
+                }
+                if (!good) {
+                    errors++;
+                }
+            }
+
+            if (errors > 0) {
+                data.setValue(classBase + ".qualified", cl.qualifiedName());
+                data.setValue(classBase + ".errorCount", ""+errors);
+                data.setValue(classBase + ".totalCount", ""+total);
+                data.setValue(classBase + ".percentGood", percent(errors, total));
+            }
+
+            PackageInfo pkg = cl.containingPackage();
+            String pkgName = pkg != null ? pkg.name() : "";
+            PackageStats ps = packageStats.get(pkgName);
+            if (ps == null) {
+                ps = new PackageStats();
+                ps.name = pkgName;
+                packageStats.put(pkgName, ps);
+            }
+            ps.total += total;
+            ps.errors += errors;
+
+            classIndex++;
+        }
+
+        int allTotal = 0;
+        int allErrors = 0;
+
+        int i = 0;
+        for (PackageStats ps: packageStats.values()) {
+            data.setValue("packages." + i + ".name", ""+ps.name);
+            data.setValue("packages." + i + ".errorCount", ""+ps.errors);
+            data.setValue("packages." + i + ".totalCount", ""+ps.total);
+            data.setValue("packages." + i + ".percentGood", percent(ps.errors, ps.total));
+
+            allTotal += ps.total;
+            allErrors += ps.errors;
+
+            i++;
+        }
+
+        data.setValue("all.errorCount", ""+allErrors);
+        data.setValue("all.totalCount", ""+allTotal);
+        data.setValue("all.percentGood", percent(allErrors, allTotal));
+
+        ClearPage.write(data, "todo.cs", filename, true);
+    }
+}
+
diff --git a/tools/droiddoc/src/TypeInfo.java b/tools/droiddoc/src/TypeInfo.java
new file mode 100644
index 0000000..c1119de
--- /dev/null
+++ b/tools/droiddoc/src/TypeInfo.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.clearsilver.HDF;
+import org.clearsilver.CS;
+import java.util.*;
+import java.io.*;
+
+public class TypeInfo
+{
+    public TypeInfo(boolean isPrimitive, String dimension,
+            String simpleTypeName, String qualifiedTypeName,
+            ClassInfo cl)
+    {
+        mIsPrimitive = isPrimitive;
+        mDimension = dimension;
+        mSimpleTypeName = simpleTypeName;
+        mQualifiedTypeName = qualifiedTypeName;
+        mClass = cl;
+    }
+
+    public ClassInfo asClassInfo()
+    {
+        return mClass;
+    }
+
+    public boolean isPrimitive()
+    {
+        return mIsPrimitive;
+    }
+
+    public String dimension()
+    {
+        return mDimension;
+    }
+
+    public String simpleTypeName()
+    {
+        return mSimpleTypeName;
+    }
+
+    public String qualifiedTypeName()
+    {
+        return mQualifiedTypeName;
+    }
+
+    public String fullName()
+    {
+        if (mFullName != null) {
+            return mFullName;
+        } else {
+            return fullName(new HashSet());
+        }
+    }
+
+    public static String typeArgumentsName(TypeInfo[] args, HashSet<String> typeVars)
+    {
+        String result = "<";
+        for (int i=0; i<args.length; i++) {
+            result += args[i].fullName(typeVars);
+            if (i != args.length-1) {
+                result += ", ";
+            }
+        }
+        result += ">";
+        return result;
+    }
+
+    public String fullName(HashSet<String> typeVars)
+    {
+        mFullName = fullNameNoDimension(typeVars) + mDimension;
+        return mFullName;
+    }
+
+    public String fullNameNoDimension(HashSet<String> typeVars)
+    {
+        String fullName = null;
+        if (mIsTypeVariable) {
+            if (typeVars.contains(mQualifiedTypeName)) {
+                // don't recurse forever with the parameters.  This handles
+                // Enum<K extends Enum<K>>
+                return mQualifiedTypeName;
+            }
+            typeVars.add(mQualifiedTypeName);
+        }
+/*
+        if (fullName != null) {
+            return fullName;
+        }
+*/
+        fullName = mQualifiedTypeName;
+        if (mTypeArguments != null && mTypeArguments.length > 0) {
+            fullName += typeArgumentsName(mTypeArguments, typeVars);
+        }
+        else if (mSuperBounds != null && mSuperBounds.length > 0) {
+            fullName += " super " + mSuperBounds[0].fullName(typeVars);
+            for (int i=1; i<mSuperBounds.length; i++) {
+                fullName += " & " + mSuperBounds[i].fullName(typeVars);
+            }
+        }
+        else if (mExtendsBounds != null && mExtendsBounds.length > 0) {
+            fullName += " extends " + mExtendsBounds[0].fullName(typeVars);
+            for (int i=1; i<mExtendsBounds.length; i++) {
+                fullName += " & " + mExtendsBounds[i].fullName(typeVars);
+            }
+        }
+        return fullName;
+    }
+
+    public TypeInfo[] typeArguments()
+    {
+        return mTypeArguments;
+    }
+
+    public void makeHDF(HDF data, String base)
+    {
+        makeHDFRecursive(data, base, false, false, new HashSet<String>());
+    }
+
+    public void makeQualifiedHDF(HDF data, String base)
+    {
+        makeHDFRecursive(data, base, true, false, new HashSet<String>());
+    }
+
+    public void makeHDF(HDF data, String base, boolean isLastVararg,
+            HashSet<String> typeVariables)
+    {
+        makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
+    }
+
+    public void makeQualifiedHDF(HDF data, String base, HashSet<String> typeVariables)
+    {
+        makeHDFRecursive(data, base, true, false, typeVariables);
+    }
+
+    private void makeHDFRecursive(HDF data, String base, boolean qualified,
+            boolean isLastVararg, HashSet<String> typeVars)
+    {
+        String label = qualified ? qualifiedTypeName() : simpleTypeName();
+        label += (isLastVararg) ? "..." : dimension();
+        data.setValue(base + ".label", label);
+        ClassInfo cl = asClassInfo();
+        if (mIsTypeVariable || mIsWildcard) {
+            // could link to an @param tag on the class to describe this
+            // but for now, just don't make it a link
+        }
+        else if (!isPrimitive() && cl != null && cl.isIncluded()) {
+            data.setValue(base + ".link", cl.htmlPage());
+        }
+
+        if (mIsTypeVariable) {
+            if (typeVars.contains(qualifiedTypeName())) {
+                // don't recurse forever with the parameters.  This handles
+                // Enum<K extends Enum<K>>
+                return;
+            }
+            typeVars.add(qualifiedTypeName());
+        }
+        if (mTypeArguments != null) {
+            TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
+        }
+        if (mSuperBounds != null) {
+            TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
+        }
+        if (mExtendsBounds != null) {
+            TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
+        }
+    }
+
+    public static void makeHDF(HDF data, String base, TypeInfo[] types, boolean qualified,
+            HashSet<String> typeVariables)
+    {
+        final int N = types.length;
+        for (int i=0; i<N; i++) {
+            types[i].makeHDFRecursive(data, base + "." + i, qualified, false, typeVariables);
+        }
+    }
+
+    public static void makeHDF(HDF data, String base, TypeInfo[] types, boolean qualified)
+    {
+        makeHDF(data, base, types, qualified, new HashSet<String>());
+    }
+
+    void setTypeArguments(TypeInfo[] args)
+    {
+        mTypeArguments = args;
+    }
+
+    void setBounds(TypeInfo[] superBounds, TypeInfo[] extendsBounds)
+    {
+        mSuperBounds = superBounds;
+        mExtendsBounds = extendsBounds;
+    }
+
+    void setIsTypeVariable(boolean b)
+    {
+        mIsTypeVariable = b;
+    }
+
+    void setIsWildcard(boolean b)
+    {
+        mIsWildcard = b;
+    }
+
+    static HashSet<String> typeVariables(TypeInfo[] params)
+    {
+        return typeVariables(params, new HashSet());
+    }
+
+    static HashSet<String> typeVariables(TypeInfo[] params, HashSet<String> result)
+    {
+        for (TypeInfo t: params) {
+            if (t.mIsTypeVariable) {
+                result.add(t.mQualifiedTypeName);
+            }
+        }
+        return result;
+    }
+
+
+    public boolean isTypeVariable()
+    {
+        return mIsTypeVariable;
+    }
+
+    public String defaultValue() {
+        if (mIsPrimitive) {
+            if ("boolean".equals(mSimpleTypeName)) {
+                return "false";
+            } else {
+                return "0";
+            }
+        } else {
+            return "null";
+        }
+    }
+
+    public String toString(){
+      String returnString = "";
+      returnString += "Primitive?: " + mIsPrimitive + " TypeVariable?: " +
+      mIsTypeVariable + " Wildcard?: " + mIsWildcard + " Dimension: " + mDimension
+      + " QualifedTypeName: " + mQualifiedTypeName;
+
+      if (mTypeArguments != null){
+        returnString += "\nTypeArguments: ";
+        for (TypeInfo tA : mTypeArguments){
+          returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
+        }
+      }
+      if (mSuperBounds != null){
+        returnString += "\nSuperBounds: ";
+        for (TypeInfo tA : mSuperBounds){
+          returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
+        }
+      }
+      if (mExtendsBounds != null){
+        returnString += "\nExtendsBounds: ";
+        for (TypeInfo tA : mExtendsBounds){
+          returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
+        }
+      }
+      return returnString;
+    }
+
+    private boolean mIsPrimitive;
+    private boolean mIsTypeVariable;
+    private boolean mIsWildcard;
+    private String mDimension;
+    private String mSimpleTypeName;
+    private String mQualifiedTypeName;
+    private ClassInfo mClass;
+    private TypeInfo[] mTypeArguments;
+    private TypeInfo[] mSuperBounds;
+    private TypeInfo[] mExtendsBounds;
+    private String mFullName;
+}