/* * 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_CONSTANT_TYPE_FEATURE = "android.annotation.SdkConstant.SdkConstantType.FEATURE"; 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 mHDFData = new ArrayList(); public static Map escapeChars = new HashMap(); public static String title = ""; public static SinceTagger sinceTagger = new SinceTagger(); public static HashSet knownTags = new HashSet(); private static boolean parseComments = false; private static boolean generateDocs = true; /** * Returns true if we should parse javadoc comments, * reporting errors in the process. */ public static boolean parseComments() { return generateDocs || parseComments; } 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 sampleCodes = new ArrayList(); String stubsDir = null; //Create the dependency graph for the stubs directory boolean apiXML = false; boolean offlineMode = false; String apiFile = null; String debugStubsFile = ""; HashSet stubPackages = null; ArrayList knownTagsFiles = new ArrayList(); 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("-knowntags")) { knownTagsFiles.add(a[1]); } 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.htmlDirs.add(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]; } else if (a[0].equals("-nodocs")) { generateDocs = false; } else if (a[0].equals("-parsecomments")) { parseComments = true; } else if (a[0].equals("-since")) { sinceTagger.addVersion(a[1], a[2]); } else if (a[0].equals("-offlinemode")) { offlineMode = true; } } if (!readKnownTagsFiles(knownTags, knownTagsFiles)) { return false; } // read some prefs from the template if (!readTemplateSettings()) { return false; } // Set up the data structures Converter.makeInfo(r); if (generateDocs) { long startTime = System.nanoTime(); // Apply @since tags from the XML file sinceTagger.tagAll(Converter.rootClasses()); // Files for proofreading if (proofreadFile != null) { Proofread.initProofread(proofreadFile); } if (todoFile != null) { TodoFile.writeTodoFile(todoFile); } // HTML Pages if (!ClearPage.htmlDirs.isEmpty()) { writeHTMLPages(); } // Navigation tree NavTree.writeNavTree(javadocDir); // Packages Pages writePackages(javadocDir + (!ClearPage.htmlDirs.isEmpty() ? "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(offlineMode); } // Index page writeIndex(); Proofread.finishProofread(proofreadFile); if (sdkValuePath != null) { writeSdkValues(sdkValuePath); } long time = System.nanoTime() - startTime; System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to " + ClearPage.outputDir); } // Stubs if (stubsDir != null) { Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages); } 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; } private static boolean readKnownTagsFiles(HashSet knownTags, ArrayList knownTagsFiles) { for (String fn: knownTagsFiles) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(fn)); int lineno = 0; boolean fail = false; while (true) { lineno++; String line = in.readLine(); if (line == null) { break; } line = line.trim(); if (line.length() == 0) { continue; } else if (line.charAt(0) == '#') { continue; } String[] words = line.split("\\s+", 2); if (words.length == 2) { if (words[1].charAt(0) != '#') { System.err.println(fn + ":" + lineno + ": Only one tag allowed per line: " + line); fail = true; continue; } } knownTags.add(words[0]); } if (fail) { return false; } } catch (IOException ex) { System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")"); return false; } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } 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 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("-knowntags")) { return 2; } 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; } if (option.equals("-nodocs")) { return 1; } if (option.equals("-parsecomments")) { return 1; } if (option.equals("-since")) { return 3; } if (option.equals("-offlinemode")) { return 1; } 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 sorted = new TreeMap(); 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()); data.setValue("docs.packages." + i + ".since", pkg.getSince()); TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); i++; } sinceTagger.writeVersionNames(data); return data; } public static void writeDirectory(File dir, String relative) { File[] files = dir.listFiles(); int i, count = files.length; for (i=0; i 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() { for (String htmlDir : ClearPage.htmlDirs) { File f = new File(htmlDir); if (!f.isDirectory()) { System.err.println("htmlDir not a directory: " + htmlDir); continue; } writeDirectory(f, ""); } } public static void writeLists() { HDF data = makeHDF(); ClassInfo[] classes = Converter.rootClasses(); SortedMap sorted = new TreeMap(); 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 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 notStrippable = new HashSet(); 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 sorted = new TreeMap(); for (ClassInfo cl: classes) { PackageInfo pkg = cl.containingPackage(); String name; if (pkg == null) { name = ""; } else { name = pkg.name(); } sorted.put(name, pkg); } ArrayList 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.since", pkg.getSince()); 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 keywords = new ArrayList(); 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 info = new ArrayList(); 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