/* * 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 HashSet<String> knownTags = new HashSet<String>(); 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<SampleCode> sampleCodes = new ArrayList<SampleCode>(); String stubsDir = null; //Create the dependency graph for the stubs directory boolean apiXML = false; boolean offlineMode = false; String apiFile = null; String debugStubsFile = ""; HashSet<String> stubPackages = null; ArrayList<String> knownTagsFiles = new ArrayList<String>(); 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<String> knownTags, ArrayList<String> 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<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("-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<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() { 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<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; } }