diff options
Diffstat (limited to 'tools/droiddoc/src/DroidDoc.java')
-rw-r--r-- | tools/droiddoc/src/DroidDoc.java | 1342 |
1 files changed, 1342 insertions, 0 deletions
diff --git a/tools/droiddoc/src/DroidDoc.java b/tools/droiddoc/src/DroidDoc.java new file mode 100644 index 0000000..f664c41 --- /dev/null +++ b/tools/droiddoc/src/DroidDoc.java @@ -0,0 +1,1342 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.sun.javadoc.*; + +import org.clearsilver.HDF; + +import java.util.*; +import java.io.*; +import java.lang.reflect.Proxy; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class DroidDoc +{ + private static final String SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant"; + private static final String SDK_CONSTANT_TYPE_ACTIVITY_ACTION = "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_BROADCAST_ACTION = "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_SERVICE_ACTION = "android.annotation.SdkConstant.SdkConstantType.SERVICE_INTENT_ACTION"; + private static final String SDK_CONSTANT_TYPE_CATEGORY = "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY"; + private static final String SDK_WIDGET_ANNOTATION = "android.annotation.Widget"; + private static final String SDK_LAYOUT_ANNOTATION = "android.annotation.Layout"; + + private static final int TYPE_NONE = 0; + private static final int TYPE_WIDGET = 1; + private static final int TYPE_LAYOUT = 2; + private static final int TYPE_LAYOUT_PARAM = 3; + + public static final int SHOW_PUBLIC = 0x00000001; + public static final int SHOW_PROTECTED = 0x00000003; + public static final int SHOW_PACKAGE = 0x00000007; + public static final int SHOW_PRIVATE = 0x0000000f; + public static final int SHOW_HIDDEN = 0x0000001f; + + public static int showLevel = SHOW_PROTECTED; + + public static final String javadocDir = "reference/"; + public static String htmlExtension; + + public static RootDoc root; + public static ArrayList<String[]> mHDFData = new ArrayList<String[]>(); + public static Map<Character,String> escapeChars = new HashMap<Character,String>(); + public static String title = ""; + + public static boolean checkLevel(int level) + { + return (showLevel & level) == level; + } + + public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, + boolean priv, boolean hidden) + { + int level = 0; + if (hidden && !checkLevel(SHOW_HIDDEN)) { + return false; + } + if (pub && checkLevel(SHOW_PUBLIC)) { + return true; + } + if (prot && checkLevel(SHOW_PROTECTED)) { + return true; + } + if (pkgp && checkLevel(SHOW_PACKAGE)) { + return true; + } + if (priv && checkLevel(SHOW_PRIVATE)) { + return true; + } + return false; + } + + public static boolean start(RootDoc r) + { + String keepListFile = null; + String proofreadFile = null; + String todoFile = null; + String sdkValuePath = null; + ArrayList<SampleCode> sampleCodes = new ArrayList<SampleCode>(); + String stubsDir = null; + //Create the dependency graph for the stubs directory + boolean apiXML = false; + String apiFile = null; + String debugStubsFile = ""; + HashSet<String> stubPackages = null; + + root = r; + + String[][] options = r.options(); + for (String[] a: options) { + if (a[0].equals("-d")) { + ClearPage.outputDir = a[1]; + } + else if (a[0].equals("-templatedir")) { + ClearPage.addTemplateDir(a[1]); + } + else if (a[0].equals("-hdf")) { + mHDFData.add(new String[] {a[1], a[2]}); + } + else if (a[0].equals("-toroot")) { + ClearPage.toroot = a[1]; + } + else if (a[0].equals("-samplecode")) { + sampleCodes.add(new SampleCode(a[1], a[2], a[3])); + } + else if (a[0].equals("-htmldir")) { + ClearPage.htmlDir = a[1]; + } + else if (a[0].equals("-title")) { + DroidDoc.title = a[1]; + } + else if (a[0].equals("-werror")) { + Errors.setWarningsAreErrors(true); + } + else if (a[0].equals("-error") || a[0].equals("-warning") + || a[0].equals("-hide")) { + try { + int level = -1; + if (a[0].equals("-error")) { + level = Errors.ERROR; + } + else if (a[0].equals("-warning")) { + level = Errors.WARNING; + } + else if (a[0].equals("-hide")) { + level = Errors.HIDDEN; + } + Errors.setErrorLevel(Integer.parseInt(a[1]), level); + } + catch (NumberFormatException e) { + // already printed below + return false; + } + } + else if (a[0].equals("-keeplist")) { + keepListFile = a[1]; + } + else if (a[0].equals("-proofread")) { + proofreadFile = a[1]; + } + else if (a[0].equals("-todo")) { + todoFile = a[1]; + } + else if (a[0].equals("-public")) { + showLevel = SHOW_PUBLIC; + } + else if (a[0].equals("-protected")) { + showLevel = SHOW_PROTECTED; + } + else if (a[0].equals("-package")) { + showLevel = SHOW_PACKAGE; + } + else if (a[0].equals("-private")) { + showLevel = SHOW_PRIVATE; + } + else if (a[0].equals("-hidden")) { + showLevel = SHOW_HIDDEN; + } + else if (a[0].equals("-stubs")) { + stubsDir = a[1]; + } + else if (a[0].equals("-stubpackages")) { + stubPackages = new HashSet(); + for (String pkg: a[1].split(":")) { + stubPackages.add(pkg); + } + } + else if (a[0].equals("-sdkvalues")) { + sdkValuePath = a[1]; + } + else if (a[0].equals("-apixml")) { + apiXML = true; + apiFile = a[1]; + } + } + + // read some prefs from the template + if (!readTemplateSettings()) { + return false; + } + + // Set up the data structures + Converter.makeInfo(r); + + // Files for proofreading + if (proofreadFile != null) { + Proofread.initProofread(proofreadFile); + } + if (todoFile != null) { + TodoFile.writeTodoFile(todoFile); + } + + // HTML Pages + if (ClearPage.htmlDir != null) { + writeHTMLPages(); + } + + // Navigation tree + NavTree.writeNavTree(javadocDir); + + // Packages Pages + writePackages(javadocDir + + (ClearPage.htmlDir!=null + ? "packages" + htmlExtension + : "index" + htmlExtension)); + + // Classes + writeClassLists(); + writeClasses(); + writeHierarchy(); + // writeKeywords(); + + // Lists for JavaScript + writeLists(); + if (keepListFile != null) { + writeKeepList(keepListFile); + } + + // Sample Code + for (SampleCode sc: sampleCodes) { + sc.write(); + } + + // Index page + writeIndex(); + + Proofread.finishProofread(proofreadFile); + + // Stubs + if (stubsDir != null) { + Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages); + } + + if (sdkValuePath != null) { + writeSdkValues(sdkValuePath); + } + + Errors.printErrors(); + return !Errors.hadError; + } + + private static void writeIndex() { + HDF data = makeHDF(); + ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension); + } + + private static boolean readTemplateSettings() + { + HDF data = makeHDF(); + htmlExtension = data.getValue("template.extension", ".html"); + int i=0; + while (true) { + String k = data.getValue("template.escape." + i + ".key", ""); + String v = data.getValue("template.escape." + i + ".value", ""); + if ("".equals(k)) { + break; + } + if (k.length() != 1) { + System.err.println("template.escape." + i + ".key must have a length of 1: " + k); + return false; + } + escapeChars.put(k.charAt(0), v); + i++; + } + return true; + } + + public static String escape(String s) { + if (escapeChars.size() == 0) { + return s; + } + StringBuffer b = null; + int begin = 0; + final int N = s.length(); + for (int i=0; i<N; i++) { + char c = s.charAt(i); + String mapped = escapeChars.get(c); + if (mapped != null) { + if (b == null) { + b = new StringBuffer(s.length() + mapped.length()); + } + if (begin != i) { + b.append(s.substring(begin, i)); + } + b.append(mapped); + begin = i+1; + } + } + if (b != null) { + if (begin != N) { + b.append(s.substring(begin, N)); + } + return b.toString(); + } + return s; + } + + public static void setPageTitle(HDF data, String title) + { + String s = title; + if (DroidDoc.title.length() > 0) { + s += " - " + DroidDoc.title; + } + data.setValue("page.title", s); + } + + public static LanguageVersion languageVersion() + { + return LanguageVersion.JAVA_1_5; + } + + public static int optionLength(String option) + { + if (option.equals("-d")) { + return 2; + } + if (option.equals("-templatedir")) { + return 2; + } + if (option.equals("-hdf")) { + return 3; + } + if (option.equals("-toroot")) { + return 2; + } + if (option.equals("-samplecode")) { + return 4; + } + if (option.equals("-htmldir")) { + return 2; + } + if (option.equals("-title")) { + return 2; + } + if (option.equals("-werror")) { + return 1; + } + if (option.equals("-hide")) { + return 2; + } + if (option.equals("-warning")) { + return 2; + } + if (option.equals("-error")) { + return 2; + } + if (option.equals("-keeplist")) { + return 2; + } + if (option.equals("-proofread")) { + return 2; + } + if (option.equals("-todo")) { + return 2; + } + if (option.equals("-public")) { + return 1; + } + if (option.equals("-protected")) { + return 1; + } + if (option.equals("-package")) { + return 1; + } + if (option.equals("-private")) { + return 1; + } + if (option.equals("-hidden")) { + return 1; + } + if (option.equals("-stubs")) { + return 2; + } + if (option.equals("-stubpackages")) { + return 2; + } + if (option.equals("-sdkvalues")) { + return 2; + } + if (option.equals("-apixml")) { + return 2; + } + return 0; + } + + public static boolean validOptions(String[][] options, DocErrorReporter r) + { + for (String[] a: options) { + if (a[0].equals("-error") || a[0].equals("-warning") + || a[0].equals("-hide")) { + try { + Integer.parseInt(a[1]); + } + catch (NumberFormatException e) { + r.printError("bad -" + a[0] + " value must be a number: " + + a[1]); + return false; + } + } + } + + return true; + } + + public static HDF makeHDF() + { + HDF data = new HDF(); + + for (String[] p: mHDFData) { + data.setValue(p[0], p[1]); + } + + try { + for (String p: ClearPage.hdfFiles) { + data.readFile(p); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + + return data; + } + + public static HDF makePackageHDF() + { + HDF data = makeHDF(); + ClassInfo[] classes = Converter.rootClasses(); + + SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); + for (ClassInfo cl: classes) { + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + int i = 0; + for (String s: sorted.keySet()) { + PackageInfo pkg = sorted.get(s); + + if (pkg.isHidden()) { + continue; + } + Boolean allHidden = true; + int pass = 0; + ClassInfo[] classesToCheck = null; + while (pass < 5 ) { + switch(pass) { + case 0: + classesToCheck = pkg.ordinaryClasses(); + break; + case 1: + classesToCheck = pkg.enums(); + break; + case 2: + classesToCheck = pkg.errors(); + break; + case 3: + classesToCheck = pkg.exceptions(); + break; + case 4: + classesToCheck = pkg.interfaces(); + break; + default: + System.err.println("Error reading package: " + pkg.name()); + break; + } + for (ClassInfo cl : classesToCheck) { + if (!cl.isHidden()) { + allHidden = false; + break; + } + } + if (!allHidden) { + break; + } + pass++; + } + if (allHidden) { + continue; + } + + data.setValue("reference", "true"); + data.setValue("docs.packages." + i + ".name", s); + data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); + TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", + pkg.firstSentenceTags()); + i++; + } + + return data; + } + + public static void writeDirectory(File dir, String relative) + { + File[] files = dir.listFiles(); + int i, count = files.length; + for (i=0; i<count; i++) { + File f = files[i]; + if (f.isFile()) { + String templ = relative + f.getName(); + int len = templ.length(); + if (len > 3 && ".cs".equals(templ.substring(len-3))) { + HDF data = makeHDF(); + String filename = templ.substring(0,len-3) + htmlExtension; + ClearPage.write(data, templ, filename); + } + else if (len > 3 && ".jd".equals(templ.substring(len-3))) { + String filename = templ.substring(0,len-3) + htmlExtension; + DocFile.writePage(f.getAbsolutePath(), relative, filename); + } + else { + ClearPage.copyFile(f, templ); + } + } + else if (f.isDirectory()) { + writeDirectory(f, relative + f.getName() + "/"); + } + } + } + + public static void writeHTMLPages() + { + File f = new File(ClearPage.htmlDir); + if (!f.isDirectory()) { + System.err.println("htmlDir not a directory: " + ClearPage.htmlDir); + } + writeDirectory(f, ""); + } + + public static void writeLists() + { + HDF data = makeHDF(); + + ClassInfo[] classes = Converter.rootClasses(); + + SortedMap<String, Object> sorted = new TreeMap<String, Object>(); + for (ClassInfo cl: classes) { + if (cl.isHidden()) { + continue; + } + sorted.put(cl.qualifiedName(), cl); + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + int i = 0; + for (String s: sorted.keySet()) { + data.setValue("docs.pages." + i + ".id" , ""+i); + data.setValue("docs.pages." + i + ".label" , s); + + Object o = sorted.get(s); + if (o instanceof PackageInfo) { + PackageInfo pkg = (PackageInfo)o; + data.setValue("docs.pages." + i + ".link" , pkg.htmlPage()); + data.setValue("docs.pages." + i + ".type" , "package"); + } + else if (o instanceof ClassInfo) { + ClassInfo cl = (ClassInfo)o; + data.setValue("docs.pages." + i + ".link" , cl.htmlPage()); + data.setValue("docs.pages." + i + ".type" , "class"); + } + i++; + } + + ClearPage.write(data, "lists.cs", javadocDir + "lists.js"); + } + + public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable) { + if (!notStrippable.add(cl)) { + // slight optimization: if it already contains cl, it already contains + // all of cl's parents + return; + } + ClassInfo supr = cl.superclass(); + if (supr != null) { + cantStripThis(supr, notStrippable); + } + for (ClassInfo iface: cl.interfaces()) { + cantStripThis(iface, notStrippable); + } + } + + private static String getPrintableName(ClassInfo cl) { + ClassInfo containingClass = cl.containingClass(); + if (containingClass != null) { + // This is an inner class. + String baseName = cl.name(); + baseName = baseName.substring(baseName.lastIndexOf('.') + 1); + return getPrintableName(containingClass) + '$' + baseName; + } + return cl.qualifiedName(); + } + + /** + * Writes the list of classes that must be present in order to + * provide the non-hidden APIs known to javadoc. + * + * @param filename the path to the file to write the list to + */ + public static void writeKeepList(String filename) { + HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>(); + ClassInfo[] all = Converter.allClasses(); + Arrays.sort(all); // just to make the file a little more readable + + // If a class is public and not hidden, then it and everything it derives + // from cannot be stripped. Otherwise we can strip it. + for (ClassInfo cl: all) { + if (cl.isPublic() && !cl.isHidden()) { + cantStripThis(cl, notStrippable); + } + } + PrintStream stream = null; + try { + stream = new PrintStream(filename); + for (ClassInfo cl: notStrippable) { + stream.println(getPrintableName(cl)); + } + } + catch (FileNotFoundException e) { + System.err.println("error writing file: " + filename); + } + finally { + if (stream != null) { + stream.close(); + } + } + } + + private static PackageInfo[] sVisiblePackages = null; + public static PackageInfo[] choosePackages() { + if (sVisiblePackages != null) { + return sVisiblePackages; + } + + ClassInfo[] classes = Converter.rootClasses(); + SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); + for (ClassInfo cl: classes) { + PackageInfo pkg = cl.containingPackage(); + String name; + if (pkg == null) { + name = ""; + } else { + name = pkg.name(); + } + sorted.put(name, pkg); + } + + ArrayList<PackageInfo> result = new ArrayList(); + + for (String s: sorted.keySet()) { + PackageInfo pkg = sorted.get(s); + + if (pkg.isHidden()) { + continue; + } + Boolean allHidden = true; + int pass = 0; + ClassInfo[] classesToCheck = null; + while (pass < 5 ) { + switch(pass) { + case 0: + classesToCheck = pkg.ordinaryClasses(); + break; + case 1: + classesToCheck = pkg.enums(); + break; + case 2: + classesToCheck = pkg.errors(); + break; + case 3: + classesToCheck = pkg.exceptions(); + break; + case 4: + classesToCheck = pkg.interfaces(); + break; + default: + System.err.println("Error reading package: " + pkg.name()); + break; + } + for (ClassInfo cl : classesToCheck) { + if (!cl.isHidden()) { + allHidden = false; + break; + } + } + if (!allHidden) { + break; + } + pass++; + } + if (allHidden) { + continue; + } + + result.add(pkg); + } + + sVisiblePackages = result.toArray(new PackageInfo[result.size()]); + return sVisiblePackages; + } + + public static void writePackages(String filename) + { + HDF data = makePackageHDF(); + + int i = 0; + for (PackageInfo pkg: choosePackages()) { + writePackage(pkg); + + data.setValue("docs.packages." + i + ".name", pkg.name()); + data.setValue("docs.packages." + i + ".link", pkg.htmlPage()); + TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", + pkg.firstSentenceTags()); + + i++; + } + + setPageTitle(data, "Package Index"); + + TagInfo.makeHDF(data, "root.descr", + Converter.convertTags(root.inlineTags(), null)); + + ClearPage.write(data, "packages.cs", filename); + ClearPage.write(data, "package-list.cs", javadocDir + "package-list"); + + Proofread.writePackages(filename, + Converter.convertTags(root.inlineTags(), null)); + } + + public static void writePackage(PackageInfo pkg) + { + // these this and the description are in the same directory, + // so it's okay + HDF data = makePackageHDF(); + + String name = pkg.name(); + + data.setValue("package.name", name); + data.setValue("package.descr", "...description..."); + + makeClassListHDF(data, "package.interfaces", + ClassInfo.sortByName(pkg.interfaces())); + makeClassListHDF(data, "package.classes", + ClassInfo.sortByName(pkg.ordinaryClasses())); + makeClassListHDF(data, "package.enums", + ClassInfo.sortByName(pkg.enums())); + makeClassListHDF(data, "package.exceptions", + ClassInfo.sortByName(pkg.exceptions())); + makeClassListHDF(data, "package.errors", + ClassInfo.sortByName(pkg.errors())); + TagInfo.makeHDF(data, "package.shortDescr", + pkg.firstSentenceTags()); + TagInfo.makeHDF(data, "package.descr", pkg.inlineTags()); + + String filename = pkg.htmlPage(); + setPageTitle(data, name); + ClearPage.write(data, "package.cs", filename); + + filename = pkg.fullDescriptionHtmlPage(); + setPageTitle(data, name + " Details"); + ClearPage.write(data, "package-descr.cs", filename); + + Proofread.writePackage(filename, pkg.inlineTags()); + } + + public static void writeClassLists() + { + int i; + HDF data = makePackageHDF(); + + ClassInfo[] classes = PackageInfo.filterHidden( + Converter.convertClasses(root.classes())); + if (classes.length == 0) { + return ; + } + + Sorter[] sorted = new Sorter[classes.length]; + for (i=0; i<sorted.length; i++) { + ClassInfo cl = classes[i]; + String name = cl.name(); + sorted[i] = new Sorter(name, cl); + } + + Arrays.sort(sorted); + + // make a pass and resolve ones that have the same name + int firstMatch = 0; + String lastName = sorted[0].label; + for (i=1; i<sorted.length; i++) { + String s = sorted[i].label; + if (!lastName.equals(s)) { + if (firstMatch != i-1) { + // there were duplicates + for (int j=firstMatch; j<i; j++) { + PackageInfo pkg = ((ClassInfo)sorted[j].data).containingPackage(); + if (pkg != null) { + sorted[j].label = sorted[j].label + " (" + pkg.name() + ")"; + } + } + } + firstMatch = i; + lastName = s; + } + } + + // and sort again + Arrays.sort(sorted); + + for (i=0; i<sorted.length; i++) { + String s = sorted[i].label; + ClassInfo cl = (ClassInfo)sorted[i].data; + char first = Character.toUpperCase(s.charAt(0)); + cl.makeShortDescrHDF(data, "docs.classes." + first + '.' + i); + } + + setPageTitle(data, "Class Index"); + ClearPage.write(data, "classes.cs", javadocDir + "classes" + htmlExtension); + } + + // we use the word keywords because "index" means something else in html land + // the user only ever sees the word index +/* public static void writeKeywords() + { + ArrayList<KeywordEntry> keywords = new ArrayList<KeywordEntry>(); + + ClassInfo[] classes = PackageInfo.filterHidden(Converter.convertClasses(root.classes())); + + for (ClassInfo cl: classes) { + cl.makeKeywordEntries(keywords); + } + + HDF data = makeHDF(); + + Collections.sort(keywords); + + int i=0; + for (KeywordEntry entry: keywords) { + String base = "keywords." + entry.firstChar() + "." + i; + entry.makeHDF(data, base); + i++; + } + + setPageTitle(data, "Index"); + ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + htmlExtension); + } */ + + public static void writeHierarchy() + { + ClassInfo[] classes = Converter.rootClasses(); + ArrayList<ClassInfo> info = new ArrayList<ClassInfo>(); + for (ClassInfo cl: classes) { + if (!cl.isHidden()) { + info.add(cl); + } + } + HDF data = makePackageHDF(); + Hierarchy.makeHierarchy(data, info.toArray(new ClassInfo[info.size()])); + setPageTitle(data, "Class Hierarchy"); + ClearPage.write(data, "hierarchy.cs", javadocDir + "hierarchy" + htmlExtension); + } + + public static void writeClasses() + { + ClassInfo[] classes = Converter.rootClasses(); + + for (ClassInfo cl: classes) { + HDF data = makePackageHDF(); + if (!cl.isHidden()) { + writeClass(cl, data); + } + } + } + + public static void writeClass(ClassInfo cl, HDF data) + { + cl.makeHDF(data); + + setPageTitle(data, cl.name()); + ClearPage.write(data, "class.cs", cl.htmlPage()); + + Proofread.writeClass(cl.htmlPage(), cl); + } + + public static void makeClassListHDF(HDF data, String base, + ClassInfo[] classes) + { + for (int i=0; i<classes.length; i++) { + ClassInfo cl = classes[i]; + if (!cl.isHidden()) { + cl.makeShortDescrHDF(data, base + "." + i); + } + } + } + + public static String linkTarget(String source, String target) + { + String[] src = source.split("/"); + String[] tgt = target.split("/"); + + int srclen = src.length; + int tgtlen = tgt.length; + + int same = 0; + while (same < (srclen-1) + && same < (tgtlen-1) + && (src[same].equals(tgt[same]))) { + same++; + } + + String s = ""; + + int up = srclen-same-1; + for (int i=0; i<up; i++) { + s += "../"; + } + + + int N = tgtlen-1; + for (int i=same; i<N; i++) { + s += tgt[i] + '/'; + } + s += tgt[tgtlen-1]; + + return s; + } + + /** + * Returns true if the given element has an @hide annotation. + */ + private static boolean hasHideAnnotation(Doc doc) { + return doc.getRawCommentText().indexOf("@hide") != -1; + } + + /** + * Returns true if the given element is hidden. + */ + private static boolean isHidden(Doc doc) { + // Methods, fields, constructors. + if (doc instanceof MemberDoc) { + return hasHideAnnotation(doc); + } + + // Classes, interfaces, enums, annotation types. + if (doc instanceof ClassDoc) { + ClassDoc classDoc = (ClassDoc) doc; + + // Check the containing package. + if (hasHideAnnotation(classDoc.containingPackage())) { + return true; + } + + // Check the class doc and containing class docs if this is a + // nested class. + ClassDoc current = classDoc; + do { + if (hasHideAnnotation(current)) { + return true; + } + + current = current.containingClass(); + } while (current != null); + } + + return false; + } + + /** + * Filters out hidden elements. + */ + private static Object filterHidden(Object o, Class<?> expected) { + if (o == null) { + return null; + } + + Class type = o.getClass(); + if (type.getName().startsWith("com.sun.")) { + // TODO: Implement interfaces from superclasses, too. + return Proxy.newProxyInstance(type.getClassLoader(), + type.getInterfaces(), new HideHandler(o)); + } else if (o instanceof Object[]) { + Class<?> componentType = expected.getComponentType(); + Object[] array = (Object[]) o; + List<Object> list = new ArrayList<Object>(array.length); + for (Object entry : array) { + if ((entry instanceof Doc) && isHidden((Doc) entry)) { + continue; + } + list.add(filterHidden(entry, componentType)); + } + return list.toArray( + (Object[]) Array.newInstance(componentType, list.size())); + } else { + return o; + } + } + + /** + * Filters hidden elements out of method return values. + */ + private static class HideHandler implements InvocationHandler { + + private final Object target; + + public HideHandler(Object target) { + this.target = target; + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + if (args != null) { + if (methodName.equals("compareTo") || + methodName.equals("equals") || + methodName.equals("overrides") || + methodName.equals("subclassOf")) { + args[0] = unwrap(args[0]); + } + } + + if (methodName.equals("getRawCommentText")) { + return filterComment((String) method.invoke(target, args)); + } + + // escape "&" in disjunctive types. + if (proxy instanceof Type && methodName.equals("toString")) { + return ((String) method.invoke(target, args)) + .replace("&", "&"); + } + + 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<ClassInfo> layouts = new ArrayList<ClassInfo>(); + ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>(); + ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>(); + + ClassInfo[] classes = Converter.allClasses(); + + // Go through all the fields of all the classes, looking SDK stuff. + for (ClassInfo clazz : classes) { + + // first check constant fields for the SdkConstant annotation. + FieldInfo[] fields = clazz.allSelfFields(); + for (FieldInfo field : fields) { + Object cValue = field.constantValue(); + if (cValue != null) { + AnnotationInstanceInfo[] annotations = field.annotations(); + if (annotations.length > 0) { + for (AnnotationInstanceInfo annotation : annotations) { + if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { + AnnotationValueInfo[] values = annotation.elementValues(); + if (values.length > 0) { + String type = values[0].valueString(); + if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) { + activityActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) { + broadcastActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) { + serviceActions.add(cValue.toString()); + } else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) { + categories.add(cValue.toString()); + } + } + break; + } + } + } + } + } + + // Now check the class for @Widget or if its in the android.widget package + // (unless the class is hidden or abstract, or non public) + if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) { + boolean annotated = false; + AnnotationInstanceInfo[] annotations = clazz.annotations(); + if (annotations.length > 0) { + for (AnnotationInstanceInfo annotation : annotations) { + if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) { + widgets.add(clazz); + annotated = true; + break; + } else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) { + layouts.add(clazz); + annotated = true; + break; + } + } + } + + if (annotated == false) { + // lets check if this is inside android.widget + PackageInfo pckg = clazz.containingPackage(); + String packageName = pckg.name(); + if ("android.widget".equals(packageName) || + "android.view".equals(packageName)) { + // now we check what this class inherits either from android.view.ViewGroup + // or android.view.View, or android.view.ViewGroup.LayoutParams + int type = checkInheritance(clazz); + switch (type) { + case TYPE_WIDGET: + widgets.add(clazz); + break; + case TYPE_LAYOUT: + layouts.add(clazz); + break; + case TYPE_LAYOUT_PARAM: + layoutParams.add(clazz); + break; + } + } + } + } + } + + // now write the files, whether or not the list are empty. + // the SDK built requires those files to be present. + + Collections.sort(activityActions); + writeValues(output + "/activity_actions.txt", activityActions); + + Collections.sort(broadcastActions); + writeValues(output + "/broadcast_actions.txt", broadcastActions); + + Collections.sort(serviceActions); + writeValues(output + "/service_actions.txt", serviceActions); + + Collections.sort(categories); + writeValues(output + "/categories.txt", categories); + + // before writing the list of classes, we do some checks, to make sure the layout params + // are enclosed by a layout class (and not one that has been declared as a widget) + for (int i = 0 ; i < layoutParams.size();) { + ClassInfo layoutParamClass = layoutParams.get(i); + ClassInfo containingClass = layoutParamClass.containingClass(); + if (containingClass == null || layouts.indexOf(containingClass) == -1) { + layoutParams.remove(i); + } else { + i++; + } + } + + writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); + } + + /** + * Writes a list of values into a text files. + * @param pathname the absolute os path of the output file. + * @param values the list of values to write. + */ + private static void writeValues(String pathname, ArrayList<String> values) { + FileWriter fw = null; + BufferedWriter bw = null; + try { + fw = new FileWriter(pathname, false); + bw = new BufferedWriter(fw); + + for (String value : values) { + bw.append(value).append('\n'); + } + } catch (IOException e) { + // pass for now + } finally { + try { + if (bw != null) bw.close(); + } catch (IOException e) { + // pass for now + } + try { + if (fw != null) fw.close(); + } catch (IOException e) { + // pass for now + } + } + } + + /** + * Writes the widget/layout/layout param classes into a text files. + * @param pathname the absolute os path of the output file. + * @param widgets the list of widget classes to write. + * @param layouts the list of layout classes to write. + * @param layoutParams the list of layout param classes to write. + */ + private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets, + ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) { + FileWriter fw = null; + BufferedWriter bw = null; + try { + fw = new FileWriter(pathname, false); + bw = new BufferedWriter(fw); + + // write the 3 types of classes. + for (ClassInfo clazz : widgets) { + writeClass(bw, clazz, 'W'); + } + for (ClassInfo clazz : layoutParams) { + writeClass(bw, clazz, 'P'); + } + for (ClassInfo clazz : layouts) { + writeClass(bw, clazz, 'L'); + } + } catch (IOException e) { + // pass for now + } finally { + try { + if (bw != null) bw.close(); + } catch (IOException e) { + // pass for now + } + try { + if (fw != null) fw.close(); + } catch (IOException e) { + // pass for now + } + } + } + + /** + * Writes a class name and its super class names into a {@link BufferedWriter}. + * @param writer the BufferedWriter to write into + * @param clazz the class to write + * @param prefix the prefix to put at the beginning of the line. + * @throws IOException + */ + private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix) + throws IOException { + writer.append(prefix).append(clazz.qualifiedName()); + ClassInfo superClass = clazz; + while ((superClass = superClass.superclass()) != null) { + writer.append(' ').append(superClass.qualifiedName()); + } + writer.append('\n'); + } + + /** + * Checks the inheritance of {@link ClassInfo} objects. This method return + * <ul> + * <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li> + * <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li> + * <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li> + * <li>{@link #TYPE_NONE}: in all other cases</li> + * </ul> + * @param clazz the {@link ClassInfo} to check. + */ + private static int checkInheritance(ClassInfo clazz) { + if ("android.view.ViewGroup".equals(clazz.qualifiedName())) { + return TYPE_LAYOUT; + } else if ("android.view.View".equals(clazz.qualifiedName())) { + return TYPE_WIDGET; + } else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { + return TYPE_LAYOUT_PARAM; + } + + ClassInfo parent = clazz.superclass(); + if (parent != null) { + return checkInheritance(parent); + } + + return TYPE_NONE; + } +} |