| /* |
| * 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<String[]> mHDFData = new ArrayList<String[]>(); |
| public static Map<Character,String> escapeChars = new HashMap<Character,String>(); |
| public static String title = ""; |
| public static SinceTagger sinceTagger = new SinceTagger(); |
| |
| 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; |
| boolean noDocs = false; |
| boolean offlineMode = 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]; |
| } |
| else if (a[0].equals("-nodocs")) { |
| noDocs = true; |
| } |
| else if (a[0].equals("-since")) { |
| sinceTagger.addVersion(a[1], a[2]); |
| } |
| else if (a[0].equals("-offlinemode")) { |
| offlineMode = true; |
| } |
| } |
| |
| // read some prefs from the template |
| if (!readTemplateSettings()) { |
| return false; |
| } |
| |
| // Set up the data structures |
| Converter.makeInfo(r); |
| |
| if (!noDocs) { |
| 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.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(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; |
| } |
| |
| 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; |
| } |
| if (option.equals("-nodocs")) { |
| 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<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()); |
| 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<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.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<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 or @pending annotation. |
| */ |
| private static boolean hasHideAnnotation(Doc doc) { |
| String comment = doc.getRawCommentText(); |
| return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -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("&", "&"); |
| } |
| |
| 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 += " "; |
| } |
| |
| 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<String> features = 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()); |
| } else if (SDK_CONSTANT_TYPE_FEATURE.equals(type)) { |
| features.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); |
| |
| Collections.sort(features); |
| writeValues(output + "/features.txt", features); |
| |
| // 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; |
| } |
| } |