auto import from //depot/cupcake/@135843
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;
+    }
+}