/* * Copyright (C) 2010 Google Inc. * * 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. */ package com.google.doclava; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.resourceloader.ClassResourceLoader; import com.google.clearsilver.jsilver.resourceloader.CompositeResourceLoader; import com.google.clearsilver.jsilver.resourceloader.FileSystemResourceLoader; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.sun.javadoc.*; import java.util.*; import java.util.jar.JarFile; import java.util.regex.Matcher; 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; import java.net.MalformedURLException; import java.net.URL; public class Doclava { 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 boolean SORT_BY_NAV_GROUPS = true; /* Debug output for PageMetadata, format urls from site root */ public static boolean META_DBG=false; public static String outputPathBase = "/"; public static ArrayList inputPathHtmlDirs = new ArrayList(); public static ArrayList inputPathHtmlDir2 = new ArrayList(); public static String outputPathHtmlDirs; public static String outputPathHtmlDir2; public static final String devsiteRoot = "en/"; public static String javadocDir = "reference/"; public static String htmlExtension; public static RootDoc root; public static ArrayList mHDFData = new ArrayList(); public static List sTaglist = new ArrayList(); public static ArrayList sampleCodes = new ArrayList(); public static ArrayList sampleCodeGroups = new ArrayList(); public static Data samplesNavTree; public static Map escapeChars = new HashMap(); public static String title = ""; public static SinceTagger sinceTagger = new SinceTagger(); public static HashSet knownTags = new HashSet(); public static FederationTagger federationTagger = new FederationTagger(); public static Set showAnnotations = new HashSet(); public static Set hiddenPackages = new HashSet(); public static boolean includeDefaultAssets = true; private static boolean generateDocs = true; private static boolean parseComments = false; private static String yamlNavFile = null; public static boolean documentAnnotations = false; public static String documentAnnotationsPath = null; public static Map annotationDocumentationMap = null; public static JSilver jSilver = null; private static boolean gmsRef = false; private static boolean gcmRef = false; private static boolean samplesRef = false; private static boolean sac = false; public static boolean checkLevel(int level) { return (showLevel & level) == level; } /** * Returns true if we should parse javadoc comments, * reporting errors in the process. */ public static boolean parseComments() { return generateDocs || parseComments; } public static boolean checkLevel(boolean pub, boolean prot, boolean pkgp, boolean priv, boolean hidden) { 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 void main(String[] args) { com.sun.tools.javadoc.Main.execute(args); } public static boolean start(RootDoc r) { long startTime = System.nanoTime(); String keepListFile = null; String proguardFile = null; String proofreadFile = null; String todoFile = null; String sdkValuePath = null; String stubsDir = null; // Create the dependency graph for the stubs directory boolean offlineMode = false; String apiFile = null; String removedApiFile = null; String debugStubsFile = ""; HashSet stubPackages = null; ArrayList knownTagsFiles = new ArrayList(); root = r; String[][] options = r.options(); for (String[] a : options) { if (a[0].equals("-d")) { outputPathBase = outputPathHtmlDirs = 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("-samplegroup")) { sampleCodeGroups.add(new SampleCode(null, null, a[1])); } else if (a[0].equals("-samplesdir")) { getSampleProjects(new File(a[1])); //the destination output path for main htmldir } else if (a[0].equals("-htmldir")) { inputPathHtmlDirs.add(a[1]); ClearPage.htmlDirs = inputPathHtmlDirs; //the destination output path for additional htmldir } else if (a[0].equals("-htmldir2")) { if (a[2].equals("default")) { inputPathHtmlDirs.add(a[1]); } else { inputPathHtmlDir2.add(a[1]); outputPathHtmlDir2 = a[2]; } } else if (a[0].equals("-title")) { Doclava.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("-showAnnotation")) { showAnnotations.add(a[1]); } else if (a[0].equals("-hidePackage")) { hiddenPackages.add(a[1]); } else if (a[0].equals("-proguard")) { proguardFile = 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("-api")) { apiFile = a[1]; } else if (a[0].equals("-removedApi")) { removedApiFile = a[1]; } else if (a[0].equals("-nodocs")) { generateDocs = false; } else if (a[0].equals("-nodefaultassets")) { includeDefaultAssets = 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; } else if (a[0].equals("-metadataDebug")) { META_DBG = true; } else if (a[0].equals("-federate")) { try { String name = a[1]; URL federationURL = new URL(a[2]); federationTagger.addSiteUrl(name, federationURL); } catch (MalformedURLException e) { System.err.println("Could not parse URL for federation: " + a[1]); return false; } } else if (a[0].equals("-federationapi")) { String name = a[1]; String file = a[2]; federationTagger.addSiteApi(name, file); } else if (a[0].equals("-yaml")) { yamlNavFile = a[1]; } else if (a[0].equals("-devsite")) { // Don't copy the doclava assets to devsite output (ie use proj assets only) includeDefaultAssets = false; outputPathHtmlDirs = outputPathHtmlDirs + "/" + devsiteRoot; } else if (a[0].equals("-documentannotations")) { documentAnnotations = true; documentAnnotationsPath = a[1]; } } if (!readKnownTagsFiles(knownTags, knownTagsFiles)) { return false; } // Set up the data structures Converter.makeInfo(r); if (generateDocs) { ClearPage.addBundledTemplateDir("assets/customizations"); ClearPage.addBundledTemplateDir("assets/templates"); List resourceLoaders = new ArrayList(); List templates = ClearPage.getTemplateDirs(); for (String tmpl : templates) { resourceLoaders.add(new FileSystemResourceLoader(tmpl)); } templates = ClearPage.getBundledTemplateDirs(); for (String tmpl : templates) { // TODO - remove commented line - it's here for debugging purposes // resourceLoaders.add(new FileSystemResourceLoader("/Volumes/Android/master/external/doclava/res/" + tmpl)); resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/'+tmpl)); } ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders); jSilver = new JSilver(compositeResourceLoader); if (!Doclava.readTemplateSettings()) { return false; } //startTime = System.nanoTime(); // Apply @since tags from the XML file sinceTagger.tagAll(Converter.rootClasses()); // Apply details of federated documentation federationTagger.tagAll(Converter.rootClasses()); // Files for proofreading if (proofreadFile != null) { Proofread.initProofread(proofreadFile); } if (todoFile != null) { TodoFile.writeTodoFile(todoFile); } if (samplesRef) { // always write samples without offlineMode behaviors writeSamples(false, sampleCodes, SORT_BY_NAV_GROUPS); } // HTML2 Pages -- Generate Pages from optional secondary dir if (!inputPathHtmlDir2.isEmpty()) { if (!outputPathHtmlDir2.isEmpty()) { ClearPage.outputDir = outputPathBase + "/" + outputPathHtmlDir2; } ClearPage.htmlDirs = inputPathHtmlDir2; writeHTMLPages(); ClearPage.htmlDirs = inputPathHtmlDirs; } // HTML Pages if (!ClearPage.htmlDirs.isEmpty()) { ClearPage.htmlDirs = inputPathHtmlDirs; ClearPage.outputDir = outputPathHtmlDirs; writeHTMLPages(); } writeAssets(); // Navigation tree String refPrefix = new String(); if(gmsRef){ refPrefix = "gms-"; } else if(gcmRef){ refPrefix = "gcm-"; } NavTree.writeNavTree(javadocDir, refPrefix); // Write yaml tree. if (yamlNavFile != null){ NavTree.writeYamlTree(javadocDir, yamlNavFile); } // Packages Pages writePackages(javadocDir + refPrefix + "packages" + htmlExtension); // Classes writeClassLists(); writeClasses(); writeHierarchy(); // writeKeywords(); // Lists for JavaScript writeLists(); if (keepListFile != null) { writeKeepList(keepListFile); } // Index page writeIndex(); Proofread.finishProofread(proofreadFile); if (sdkValuePath != null) { writeSdkValues(sdkValuePath); } // Write metadata for all processed files to jd_lists_unified.js in out dir if (!sTaglist.isEmpty()) { PageMetadata.WriteList(sTaglist); } } // Stubs if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null) { Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, stubPackages); } Errors.printErrors(); long time = System.nanoTime() - startTime; System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to " + outputPathBase ); return !Errors.hadError; } private static void writeIndex() { Data data = makeHDF(); ClearPage.write(data, "index.cs", javadocDir + "index" + htmlExtension); } private static boolean readTemplateSettings() { Data data = makeHDF(); // The .html extension is hard-coded in several .cs files, // and so you cannot currently set it as a property. htmlExtension = ".html"; // htmlExtension = data.getValue("template.extension", ".html"); int i = 0; while (true) { String k = data.getValue("template.escape." + i + ".key", ""); String v = data.getValue("template.escape." + i + ".value", ""); if ("".equals(k)) { break; } if (k.length() != 1) { System.err.println("template.escape." + i + ".key must have a length of 1: " + k); return false; } escapeChars.put(k.charAt(0), v); i++; } return true; } private static boolean readKnownTagsFiles(HashSet knownTags, ArrayList knownTagsFiles) { for (String fn: knownTagsFiles) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(fn)); int lineno = 0; boolean fail = false; while (true) { lineno++; String line = in.readLine(); if (line == null) { break; } line = line.trim(); if (line.length() == 0) { continue; } else if (line.charAt(0) == '#') { continue; } String[] words = line.split("\\s+", 2); if (words.length == 2) { if (words[1].charAt(0) != '#') { System.err.println(fn + ":" + lineno + ": Only one tag allowed per line: " + line); fail = true; continue; } } knownTags.add(words[0]); } if (fail) { return false; } } catch (IOException ex) { System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")"); return false; } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } return true; } public static String escape(String s) { if (escapeChars.size() == 0) { return s; } StringBuffer b = null; int begin = 0; final int N = s.length(); for (int i = 0; i < 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(Data data, String title) { String s = title; if (Doclava.title.length() > 0) { s += " - " + Doclava.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")) { samplesRef = true; return 4; } if (option.equals("-samplegroup")) { return 2; } if (option.equals("-samplesdir")) { samplesRef = true; return 2; } if (option.equals("-devsite")) { return 1; } if (option.equals("-htmldir")) { return 2; } if (option.equals("-htmldir2")) { return 3; } 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("-showAnnotation")) { return 2; } if (option.equals("-hidePackage")) { return 2; } if (option.equals("-proguard")) { 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("-api")) { return 2; } if (option.equals("-removedApi")) { return 2; } if (option.equals("-nodocs")) { return 1; } if (option.equals("-nodefaultassets")) { return 1; } if (option.equals("-parsecomments")) { return 1; } if (option.equals("-since")) { return 3; } if (option.equals("-offlinemode")) { return 1; } if (option.equals("-federate")) { return 3; } if (option.equals("-federationapi")) { return 3; } if (option.equals("-yaml")) { return 2; } if (option.equals("-devsite")) { return 1; } if (option.equals("-gmsref")) { gmsRef = true; return 1; } if (option.equals("-gcmref")) { gcmRef = true; return 1; } if (option.equals("-metadataDebug")) { return 1; } if (option.equals("-documentannotations")) { 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 Data makeHDF() { Data data = jSilver.createData(); for (String[] p : mHDFData) { data.setValue(p[0], p[1]); } return data; } public static Data makePackageHDF() { Data data = makeHDF(); ClassInfo[] classes = Converter.rootClasses(); SortedMap sorted = new TreeMap(); for (ClassInfo cl : classes) { PackageInfo pkg = cl.containingPackage(); String name; if (pkg == null) { name = ""; } else { name = pkg.name(); } sorted.put(name, pkg); } int i = 0; for (Map.Entry entry : sorted.entrySet()) { String s = entry.getKey(); PackageInfo pkg = entry.getValue(); if (pkg.isHiddenOrRemoved()) { continue; } boolean allHiddenOrRemoved = true; int pass = 0; ClassInfo[] classesToCheck = null; while (pass < 6) { 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; case 5: classesToCheck = pkg.annotations(); break; default: System.err.println("Error reading package: " + pkg.name()); break; } for (ClassInfo cl : classesToCheck) { if (!cl.isHiddenOrRemoved()) { allHiddenOrRemoved = false; break; } } if (!allHiddenOrRemoved) { break; } pass++; } if (allHiddenOrRemoved) { continue; } if(gmsRef){ data.setValue("reference.gms", "true"); } else if(gcmRef){ data.setValue("reference.gcm", "true"); } data.setValue("reference", "1"); data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0"); 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; } private static void writeDirectory(File dir, String relative, JSilver js) { 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))) { Data data = makeHDF(); String filename = templ.substring(0, len - 3) + htmlExtension; ClearPage.write(data, templ, filename, js); } else if (len > 3 && ".jd".equals(templ.substring(len - 3))) { Data data = makeHDF(); String filename = templ.substring(0, len - 3) + htmlExtension; DocFile.writePage(f.getAbsolutePath(), relative, filename, data); } else if(!f.getName().equals(".DS_Store")){ Data data = makeHDF(); String hdfValue = data.getValue("sac") == null ? "" : data.getValue("sac"); boolean allowExcepted = hdfValue.equals("true") ? true : false; ClearPage.copyFile(allowExcepted, f, templ); } } else if (f.isDirectory()) { writeDirectory(f, relative + f.getName() + "/", js); } } } 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; } ResourceLoader loader = new FileSystemResourceLoader(f); JSilver js = new JSilver(loader); writeDirectory(f, "", js); } } public static void writeAssets() { JarFile thisJar = JarUtils.jarForClass(Doclava.class, null); if ((thisJar != null) && (includeDefaultAssets)) { try { List templateDirs = ClearPage.getBundledTemplateDirs(); for (String templateDir : templateDirs) { String assetsDir = templateDir + "/assets"; JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ClearPage.outputDir + "/assets"); } } catch (IOException e) { System.err.println("Error copying assets directory."); e.printStackTrace(); return; } } //write the project-specific assets List templateDirs = ClearPage.getTemplateDirs(); for (String templateDir : templateDirs) { File assets = new File(templateDir + "/assets"); if (assets.isDirectory()) { writeDirectory(assets, "assets/", null); } } // Create the timestamp.js file based on .cs file Data timedata = Doclava.makeHDF(); ClearPage.write(timedata, "timestamp.cs", "timestamp.js"); } /** Go through the docs and generate meta-data about each page to use in search suggestions */ public static void writeLists() { // Write the lists for API references Data data = makeHDF(); ClassInfo[] classes = Converter.rootClasses(); SortedMap sorted = new TreeMap(); for (ClassInfo cl : classes) { if (cl.isHiddenOrRemoved()) { 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"); data.setValue("docs.pages." + i + ".deprecated", pkg.isDeprecated() ? "true" : "false"); } else if (o instanceof ClassInfo) { ClassInfo cl = (ClassInfo) o; data.setValue("docs.pages." + i + ".link", cl.htmlPage()); data.setValue("docs.pages." + i + ".type", "class"); data.setValue("docs.pages." + i + ".deprecated", cl.isDeprecated() ? "true" : "false"); } i++; } ClearPage.write(data, "lists.cs", javadocDir + "lists.js"); // Write the lists for JD documents (if there are HTML directories to process) if (inputPathHtmlDirs.size() > 0) { Data jddata = makeHDF(); Iterator counter = new Iterator(); for (String htmlDir : inputPathHtmlDirs) { File dir = new File(htmlDir); if (!dir.isDirectory()) { continue; } writeJdDirList(dir, jddata, counter); } ClearPage.write(jddata, "jd_lists.cs", javadocDir + "jd_lists.js"); } } private static class Iterator { int i = 0; } /** Write meta-data for a JD file, used for search suggestions */ private static void writeJdDirList(File dir, Data data, Iterator counter) { File[] files = dir.listFiles(); int i, count = files.length; // Loop all files in given directory for (i = 0; i < count; i++) { File f = files[i]; if (f.isFile()) { String filePath = f.getAbsolutePath(); String templ = f.getName(); int len = templ.length(); // If it's a .jd file we want to process if (len > 3 && ".jd".equals(templ.substring(len - 3))) { // remove the directories below the site root String webPath = filePath.substring(filePath.indexOf("docs/html/") + 10, filePath.length()); // replace .jd with .html webPath = webPath.substring(0, webPath.length() - 3) + htmlExtension; // Parse the .jd file for properties data at top of page Data hdf = Doclava.makeHDF(); String filedata = DocFile.readFile(filePath); Matcher lines = DocFile.LINE.matcher(filedata); String line = null; // Get each line to add the key-value to hdf while (lines.find()) { line = lines.group(1); if (line.length() > 0) { // Stop when we hit the body if (line.equals("@jd:body")) { break; } Matcher prop = DocFile.PROP.matcher(line); if (prop.matches()) { String key = prop.group(1); String value = prop.group(2); hdf.setValue(key, value); } else { break; } } } // done gathering page properties // Insert the goods into HDF data (title, link, tags, type) String title = hdf.getValue("page.title", ""); title = title.replaceAll("\"", "'"); // if there's a in the title, get rid of it if (title.indexOf(""); title = splitTitle[0]; for (int j = 1; j < splitTitle.length; j++) { title.concat(splitTitle[j]); } } StringBuilder tags = new StringBuilder(); String tagsList = hdf.getValue("page.tags", ""); if (!tagsList.equals("")) { tagsList = tagsList.replaceAll("\"", ""); String[] tagParts = tagsList.split(","); for (int iter = 0; iter < tagParts.length; iter++) { tags.append("\""); tags.append(tagParts[iter].trim()); tags.append("\""); if (iter < tagParts.length - 1) { tags.append(","); } } } String dirName = (webPath.indexOf("/") != -1) ? webPath.substring(0, webPath.indexOf("/")) : ""; if (!"".equals(title) && !"intl".equals(dirName) && !hdf.getBooleanValue("excludeFromSuggestions")) { data.setValue("docs.pages." + counter.i + ".label", title); data.setValue("docs.pages." + counter.i + ".link", webPath); data.setValue("docs.pages." + counter.i + ".tags", tags.toString()); data.setValue("docs.pages." + counter.i + ".type", dirName); counter.i++; } } } else if (f.isDirectory()) { writeJdDirList(f, data, counter); } } } public static void cantStripThis(ClassInfo cl, HashSet notStrippable) { if (!notStrippable.add(cl)) { // slight optimization: if it already contains cl, it already contains // all of cl's parents return; } ClassInfo supr = cl.superclass(); if (supr != null) { cantStripThis(supr, notStrippable); } for (ClassInfo iface : cl.interfaces()) { cantStripThis(iface, notStrippable); } } private static String getPrintableName(ClassInfo cl) { ClassInfo containingClass = cl.containingClass(); if (containingClass != null) { // This is an inner class. String baseName = cl.name(); baseName = baseName.substring(baseName.lastIndexOf('.') + 1); return getPrintableName(containingClass) + '$' + baseName; } return cl.qualifiedName(); } /** * Writes the list of classes that must be present in order to provide the non-hidden APIs known * to javadoc. * * @param filename the path to the file to write the list to */ public static void writeKeepList(String filename) { HashSet notStrippable = new HashSet(); ClassInfo[] all = Converter.allClasses(); Arrays.sort(all); // just to make the file a little more readable // If a class is public and not hidden, then it and everything it derives // from cannot be stripped. Otherwise we can strip it. for (ClassInfo cl : all) { if (cl.isPublic() && !cl.isHiddenOrRemoved()) { cantStripThis(cl, notStrippable); } } PrintStream stream = null; try { stream = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename))); for (ClassInfo cl : notStrippable) { stream.println(getPrintableName(cl)); } } catch (FileNotFoundException e) { System.err.println("error writing file: " + filename); } finally { if (stream != null) { stream.close(); } } } private static PackageInfo[] sVisiblePackages = null; public static PackageInfo[] choosePackages() { if (sVisiblePackages != null) { return sVisiblePackages; } ClassInfo[] classes = Converter.rootClasses(); SortedMap sorted = new TreeMap(); for (ClassInfo cl : classes) { PackageInfo pkg = cl.containingPackage(); String name; if (pkg == null) { name = ""; } else { name = pkg.name(); } sorted.put(name, pkg); } ArrayList result = new ArrayList(); for (String s : sorted.keySet()) { PackageInfo pkg = sorted.get(s); if (pkg.isHiddenOrRemoved()) { continue; } boolean allHiddenOrRemoved = true; int pass = 0; ClassInfo[] classesToCheck = null; while (pass < 6) { 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; case 5: classesToCheck = pkg.annotations(); break; default: System.err.println("Error reading package: " + pkg.name()); break; } for (ClassInfo cl : classesToCheck) { if (!cl.isHiddenOrRemoved()) { allHiddenOrRemoved = false; break; } } if (!allHiddenOrRemoved) { break; } pass++; } if (allHiddenOrRemoved) { continue; } result.add(pkg); } sVisiblePackages = result.toArray(new PackageInfo[result.size()]); return sVisiblePackages; } public static void writePackages(String filename) { Data 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 Data data = makePackageHDF(); String name = pkg.name(); data.setValue("package.name", name); data.setValue("package.since", pkg.getSince()); data.setValue("package.descr", "...description..."); pkg.setFederatedReferences(data, "package"); makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.annotations())); 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); Proofread.writePackage(filename, pkg.inlineTags()); } public static void writeClassLists() { int i; Data data = makePackageHDF(); ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved( 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 keywords = new * ArrayList(); * * ClassInfo[] classes = PackageInfo.filterHiddenAndRemoved(Converter.convertClasses(root.classes())); * * for (ClassInfo cl: classes) { cl.makeKeywordEntries(keywords); } * * HDF data = makeHDF(); * * Collections.sort(keywords); * * int i=0; for (KeywordEntry entry: keywords) { String base = "keywords." + entry.firstChar() + * "." + i; entry.makeHDF(data, base); i++; } * * setPageTitle(data, "Index"); ClearPage.write(data, "keywords.cs", javadocDir + "keywords" + * htmlExtension); } */ public static void writeHierarchy() { ClassInfo[] classes = Converter.rootClasses(); ArrayList info = new ArrayList(); for (ClassInfo cl : classes) { if (!cl.isHiddenOrRemoved()) { info.add(cl); } } Data 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) { Data data = makePackageHDF(); if (!cl.isHiddenOrRemoved()) { writeClass(cl, data); } } } public static void writeClass(ClassInfo cl, Data data) { cl.makeHDF(data); setPageTitle(data, cl.name()); String outfile = cl.htmlPage(); ClearPage.write(data, "class.cs", outfile); Proofread.writeClass(cl.htmlPage(), cl); } public static void makeClassListHDF(Data data, String base, ClassInfo[] classes) { for (int i = 0; i < classes.length; i++) { ClassInfo cl = classes[i]; if (!cl.isHiddenOrRemoved()) { 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, @removed or @pending annotation. */ private static boolean hasHideOrRemovedAnnotation(Doc doc) { String comment = doc.getRawCommentText(); return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1 || comment.indexOf("@removed") != -1; } /** * Returns true if the given element is hidden. */ private static boolean isHiddenOrRemoved(Doc doc) { // Methods, fields, constructors. if (doc instanceof MemberDoc) { return hasHideOrRemovedAnnotation(doc); } // Classes, interfaces, enums, annotation types. if (doc instanceof ClassDoc) { ClassDoc classDoc = (ClassDoc) doc; // Check the containing package. if (hasHideOrRemovedAnnotation(classDoc.containingPackage())) { return true; } // Check the class doc and containing class docs if this is a // nested class. ClassDoc current = classDoc; do { if (hasHideOrRemovedAnnotation(current)) { return true; } current = current.containingClass(); } while (current != null); } return false; } /** * Filters out hidden and removed elements. */ private static Object filterHiddenAndRemoved(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 list = new ArrayList(array.length); for (Object entry : array) { if ((entry instanceof Doc) && isHiddenOrRemoved((Doc) entry)) { continue; } list.add(filterHiddenAndRemoved(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 filterHiddenAndRemoved(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; } } /** * 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 activityActions = new ArrayList(); ArrayList broadcastActions = new ArrayList(); ArrayList serviceActions = new ArrayList(); ArrayList categories = new ArrayList(); ArrayList features = new ArrayList(); ArrayList layouts = new ArrayList(); ArrayList widgets = new ArrayList(); ArrayList layoutParams = new ArrayList(); ClassInfo[] classes = Converter.allClasses(); // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams ClassInfo topLayoutParams = null; // Go through all the fields of all the classes, looking SDK stuff. for (ClassInfo clazz : classes) { // first check constant fields for the SdkConstant annotation. ArrayList fields = clazz.allSelfFields(); for (FieldInfo field : fields) { Object cValue = field.constantValue(); if (cValue != null) { ArrayList annotations = field.annotations(); if (!annotations.isEmpty()) { for (AnnotationInstanceInfo annotation : annotations) { if (SDK_CONSTANT_ANNOTATION.equals(annotation.type().qualifiedName())) { if (!annotation.elementValues().isEmpty()) { String type = annotation.elementValues().get(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.isHiddenOrRemoved() == false && clazz.isPublic() && clazz.isAbstract() == false) { boolean annotated = false; ArrayList annotations = clazz.annotations(); if (!annotations.isEmpty()) { 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) { if (topLayoutParams == null && "android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) { topLayoutParams = clazz; } // let's check if this is inside android.widget or android.view if (isIncludedPackage(clazz)) { // 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 clazz = layoutParams.get(i); ClassInfo containingClass = clazz.containingClass(); boolean remove = containingClass == null || layouts.indexOf(containingClass) == -1; // Also ensure that super classes of the layout params are in android.widget or android.view. while (!remove && (clazz = clazz.superclass()) != null && !clazz.equals(topLayoutParams)) { remove = !isIncludedPackage(clazz); } if (remove) { layoutParams.remove(i); } else { i++; } } writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams); } /** * Check if the clazz is in package android.view or android.widget */ private static boolean isIncludedPackage(ClassInfo clazz) { String pckg = clazz.containingPackage().name(); return "android.widget".equals(pckg) || "android.view".equals(pckg); } /** * 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 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 widgets, ArrayList layouts, ArrayList 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 *
    *
  • {@link #TYPE_LAYOUT}: if the class extends android.view.ViewGroup
  • *
  • {@link #TYPE_WIDGET}: if the class extends android.view.View
  • *
  • {@link #TYPE_LAYOUT_PARAM}: if the class extends * android.view.ViewGroup$LayoutParams
  • *
  • {@link #TYPE_NONE}: in all other cases
  • *
* * @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; } /** * Ensures a trailing '/' at the end of a string. */ static String ensureSlash(String path) { return path.endsWith("/") ? path : path + "/"; } /** * Process sample projects. Generate the TOC for the samples groups and project * and write it to a cs var, which is then written to files during templating to * html output. Collect metadata from sample project _index.jd files. Copy html * and specific source file types to the output directory. */ public static void writeSamples(boolean offlineMode, ArrayList sampleCodes, boolean sortNavByGroups) { samplesNavTree = makeHDF(); // Go through samples processing files. Create a root list for SC nodes, // pass it to SCs for their NavTree children and append them. List samplesList = new ArrayList(); List sampleGroupsRootNodes = null; for (SampleCode sc : sampleCodes) { samplesList.add(sc.setSamplesTOC(offlineMode)); } if (sortNavByGroups) { sampleGroupsRootNodes = new ArrayList(); for (SampleCode gsc : sampleCodeGroups) { String link = ClearPage.toroot + "samples/" + gsc.mTitle.replaceAll(" ", "").trim().toLowerCase() + ".html"; sampleGroupsRootNodes.add(new SampleCode.Node.Builder().setLabel(gsc.mTitle).setLink(link).setType("groupholder").build()); } } // Pass full samplesList to SC to render the samples TOC to sampleNavTree hdf if (!offlineMode) { SampleCode.writeSamplesNavTree(samplesList, sampleGroupsRootNodes); } // Iterate the samplecode projects writing the files to out for (SampleCode sc : sampleCodes) { sc.writeSamplesFiles(offlineMode); } } /** * Given an initial samples directory root, walk through the directory collecting * sample code project roots and adding them to an array of SampleCodes. * @param rootDir Root directory holding all browseable sample code projects, * defined in frameworks/base/Android.mk as "-sampleDir path". */ public static void getSampleProjects(File rootDir) { for (File f : rootDir.listFiles()) { String name = f.getName(); if (f.isDirectory()) { if (isValidSampleProjectRoot(f)) { sampleCodes.add(new SampleCode(f.getAbsolutePath(), "samples/" + name, name)); } else { getSampleProjects(f); } } } } /** * Test whether a given directory is the root directory for a sample code project. * Root directories must contain a valid _index.jd file and a src/ directory * or a module directory that contains a src/ directory. */ public static boolean isValidSampleProjectRoot(File dir) { File indexJd = new File(dir, "_index.jd"); if (!indexJd.exists()) { return false; } File srcDir = new File(dir, "src"); if (srcDir.exists()) { return true; } else { // Look for a src/ directory one level below the root directory, so // modules are supported. for (File childDir : dir.listFiles()) { if (childDir.isDirectory()) { srcDir = new File(childDir, "src"); if (srcDir.exists()) { return true; } } } return false; } } public static String getDocumentationStringForAnnotation(String annotationName) { if (!documentAnnotations) return null; if (annotationDocumentationMap == null) { // parse the file for map annotationDocumentationMap = new HashMap(); try { BufferedReader in = new BufferedReader( new FileReader(documentAnnotationsPath)); try { String line = in.readLine(); String[] split; while (line != null) { split = line.split(":"); annotationDocumentationMap.put(split[0], split[1]); line = in.readLine(); } } finally { in.close(); } } catch (IOException e) { System.err.println("Unable to open annotations documentation file for reading: " + documentAnnotationsPath); } } return annotationDocumentationMap.get(annotationName); } }