Files
build/tools/droiddoc/src/DroidDoc.java
Scott Main 2fa99f1dd6 droiddoc change: revise the sample code generating script so that
all the source code pages are NOT generated for the offline version
of the docs (only show source in html for online docs).

This won't work until a companion change from framwork/base/Android.mk
is submitted (but shouldn't break the build).

Change-Id: I06c404540870071c2a5a8aa460e156506fd975cb
2009-11-20 10:41:49 -08:00

1379 lines
45 KiB
Java

/*
* 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 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("&", "&amp;");
}
try {
return filterHidden(method.invoke(target, args),
method.getReturnType());
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private String filterComment(String s) {
if (s == null) {
return null;
}
s = s.trim();
// Work around off by one error
while (s.length() >= 5
&& s.charAt(s.length() - 5) == '{') {
s += "&nbsp;";
}
return s;
}
private static Object unwrap(Object proxy) {
if (proxy instanceof Proxy)
return ((HideHandler)Proxy.getInvocationHandler(proxy)).target;
return proxy;
}
}
public static String scope(Scoped scoped) {
if (scoped.isPublic()) {
return "public";
}
else if (scoped.isProtected()) {
return "protected";
}
else if (scoped.isPackagePrivate()) {
return "";
}
else if (scoped.isPrivate()) {
return "private";
}
else {
throw new RuntimeException("invalid scope for object " + scoped);
}
}
/**
* Collect the values used by the Dev tools and write them in files packaged with the SDK
* @param output the ouput directory for the files.
*/
private static void writeSdkValues(String output) {
ArrayList<String> activityActions = new ArrayList<String>();
ArrayList<String> broadcastActions = new ArrayList<String>();
ArrayList<String> serviceActions = new ArrayList<String>();
ArrayList<String> categories = new ArrayList<String>();
ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
ClassInfo[] classes = Converter.allClasses();
// Go through all the fields of all the classes, looking SDK stuff.
for (ClassInfo clazz : classes) {
// first check constant fields for the SdkConstant annotation.
FieldInfo[] fields = clazz.allSelfFields();
for (FieldInfo field : fields) {
Object cValue = field.constantValue();
if (cValue != null) {
AnnotationInstanceInfo[] annotations = field.annotations();
if (annotations.length > 0) {
for (AnnotationInstanceInfo annotation : annotations) {
if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) {
AnnotationValueInfo[] values = annotation.elementValues();
if (values.length > 0) {
String type = values[0].valueString();
if (SDK_CONSTANT_TYPE_ACTIVITY_ACTION.equals(type)) {
activityActions.add(cValue.toString());
} else if (SDK_CONSTANT_TYPE_BROADCAST_ACTION.equals(type)) {
broadcastActions.add(cValue.toString());
} else if (SDK_CONSTANT_TYPE_SERVICE_ACTION.equals(type)) {
serviceActions.add(cValue.toString());
} else if (SDK_CONSTANT_TYPE_CATEGORY.equals(type)) {
categories.add(cValue.toString());
}
}
break;
}
}
}
}
}
// Now check the class for @Widget or if its in the android.widget package
// (unless the class is hidden or abstract, or non public)
if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) {
boolean annotated = false;
AnnotationInstanceInfo[] annotations = clazz.annotations();
if (annotations.length > 0) {
for (AnnotationInstanceInfo annotation : annotations) {
if (SDK_WIDGET_ANNOTATION.equals(annotation.type().qualifiedName())) {
widgets.add(clazz);
annotated = true;
break;
} else if (SDK_LAYOUT_ANNOTATION.equals(annotation.type().qualifiedName())) {
layouts.add(clazz);
annotated = true;
break;
}
}
}
if (annotated == false) {
// lets check if this is inside android.widget
PackageInfo pckg = clazz.containingPackage();
String packageName = pckg.name();
if ("android.widget".equals(packageName) ||
"android.view".equals(packageName)) {
// now we check what this class inherits either from android.view.ViewGroup
// or android.view.View, or android.view.ViewGroup.LayoutParams
int type = checkInheritance(clazz);
switch (type) {
case TYPE_WIDGET:
widgets.add(clazz);
break;
case TYPE_LAYOUT:
layouts.add(clazz);
break;
case TYPE_LAYOUT_PARAM:
layoutParams.add(clazz);
break;
}
}
}
}
}
// now write the files, whether or not the list are empty.
// the SDK built requires those files to be present.
Collections.sort(activityActions);
writeValues(output + "/activity_actions.txt", activityActions);
Collections.sort(broadcastActions);
writeValues(output + "/broadcast_actions.txt", broadcastActions);
Collections.sort(serviceActions);
writeValues(output + "/service_actions.txt", serviceActions);
Collections.sort(categories);
writeValues(output + "/categories.txt", categories);
// before writing the list of classes, we do some checks, to make sure the layout params
// are enclosed by a layout class (and not one that has been declared as a widget)
for (int i = 0 ; i < layoutParams.size();) {
ClassInfo layoutParamClass = layoutParams.get(i);
ClassInfo containingClass = layoutParamClass.containingClass();
if (containingClass == null || layouts.indexOf(containingClass) == -1) {
layoutParams.remove(i);
} else {
i++;
}
}
writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
}
/**
* Writes a list of values into a text files.
* @param pathname the absolute os path of the output file.
* @param values the list of values to write.
*/
private static void writeValues(String pathname, ArrayList<String> values) {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter(pathname, false);
bw = new BufferedWriter(fw);
for (String value : values) {
bw.append(value).append('\n');
}
} catch (IOException e) {
// pass for now
} finally {
try {
if (bw != null) bw.close();
} catch (IOException e) {
// pass for now
}
try {
if (fw != null) fw.close();
} catch (IOException e) {
// pass for now
}
}
}
/**
* Writes the widget/layout/layout param classes into a text files.
* @param pathname the absolute os path of the output file.
* @param widgets the list of widget classes to write.
* @param layouts the list of layout classes to write.
* @param layoutParams the list of layout param classes to write.
*/
private static void writeClasses(String pathname, ArrayList<ClassInfo> widgets,
ArrayList<ClassInfo> layouts, ArrayList<ClassInfo> layoutParams) {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter(pathname, false);
bw = new BufferedWriter(fw);
// write the 3 types of classes.
for (ClassInfo clazz : widgets) {
writeClass(bw, clazz, 'W');
}
for (ClassInfo clazz : layoutParams) {
writeClass(bw, clazz, 'P');
}
for (ClassInfo clazz : layouts) {
writeClass(bw, clazz, 'L');
}
} catch (IOException e) {
// pass for now
} finally {
try {
if (bw != null) bw.close();
} catch (IOException e) {
// pass for now
}
try {
if (fw != null) fw.close();
} catch (IOException e) {
// pass for now
}
}
}
/**
* Writes a class name and its super class names into a {@link BufferedWriter}.
* @param writer the BufferedWriter to write into
* @param clazz the class to write
* @param prefix the prefix to put at the beginning of the line.
* @throws IOException
*/
private static void writeClass(BufferedWriter writer, ClassInfo clazz, char prefix)
throws IOException {
writer.append(prefix).append(clazz.qualifiedName());
ClassInfo superClass = clazz;
while ((superClass = superClass.superclass()) != null) {
writer.append(' ').append(superClass.qualifiedName());
}
writer.append('\n');
}
/**
* Checks the inheritance of {@link ClassInfo} objects. This method return
* <ul>
* <li>{@link #TYPE_LAYOUT}: if the class extends <code>android.view.ViewGroup</code></li>
* <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
* <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li>
* <li>{@link #TYPE_NONE}: in all other cases</li>
* </ul>
* @param clazz the {@link ClassInfo} to check.
*/
private static int checkInheritance(ClassInfo clazz) {
if ("android.view.ViewGroup".equals(clazz.qualifiedName())) {
return TYPE_LAYOUT;
} else if ("android.view.View".equals(clazz.qualifiedName())) {
return TYPE_WIDGET;
} else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
return TYPE_LAYOUT_PARAM;
}
ClassInfo parent = clazz.superclass();
if (parent != null) {
return checkInheritance(parent);
}
return TYPE_NONE;
}
}