/* * Copyright (C) 2011 MuleSoft Inc. * Copyright (C) 2010 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. */ package org.mule.devkit.doclet; 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.ClassDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.DocErrorReporter; import com.sun.javadoc.LanguageVersion; import com.sun.javadoc.MemberDoc; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Type; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.mule.devkit.doclet.markdown.MarkdownProcessor; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.jar.JarFile; public class Doclava { 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 = "java/"; public static final String muleXmlDir = "mule/"; public static final String guideDir = "guide/"; public static String assetsOutputDir = "assets"; public static String htmlExtension; public static RootDoc root; public static ArrayList<String[]> mHDFData = new ArrayList<String[]>(); public static ArrayList<String[]> mMarkdown = new ArrayList<String[]>(); public static Map<Character, String> escapeChars = new HashMap<Character, String>(); public static String title = ""; public static SinceTagger sinceTagger = new SinceTagger(); public static HashSet<String> knownTags = new HashSet<String>(); public static FederationTagger federationTagger = new FederationTagger(); private static boolean generateDocs = true; private static boolean generateSources = false; private static boolean parseComments = false; public static String apiVersion = null; public static JSilver jSilver = null; 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) { String keepListFile = null; String proofreadFile = null; String todoFile = null; String stubsDir = null; // Create the dependency graph for the stubs directory String apiFile = null; HashSet<String> stubPackages = null; ArrayList<String> knownTagsFiles = new ArrayList<String>(); root = r; String[][] options = r.options(); for (String[] a : options) { if (a[0].equals("-d")) { ClearPage.outputDir = a[1]; } else if (a[0].equals("-templatedir")) { ClearPage.addTemplateDir(a[1]); } else if (a[0].equals("-hdf")) { mHDFData.add(new String[]{a[1], a[2]}); } else if (a[0].equals("-markdown")) { mMarkdown.add(new String[]{a[1], a[2], a[3]}); } 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("-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("-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<String>(); for (String pkg : a[1].split(":")) { stubPackages.add(pkg); } } else if (a[0].equals("-apixml")) { apiFile = a[1]; } else if (a[0].equals("-nodocs")) { generateDocs = false; } else if (a[0].equals("-parsecomments")) { parseComments = true; } else if (a[0].equals("-since")) { sinceTagger.addVersion(a[1], a[2]); } else if (a[0].equals("-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("-federationxml")) { String name = a[1]; String file = a[2]; federationTagger.addSiteXml(name, file); } else if (a[0].equals("-apiversion")) { apiVersion = a[1]; } else if (a[0].equals("-assetsdir")) { assetsOutputDir = a[1]; } else if (a[0].equals("-generatesources")) { generateSources = true; } } if (!readKnownTagsFiles(knownTags, knownTagsFiles)) { return false; } // Set up the data structures Converter.makeInfo(r); // Stubs and xml final File currentApiFile; if (!generateDocs && apiFile != null) { currentApiFile = new File(apiFile); } else if (generateDocs) { currentApiFile = new File(ensureSlash(ClearPage.outputDir) + javadocDir + FederatedSite.XML_API_PATH); } else { currentApiFile = null; } Stubs.writeStubsAndXml(stubsDir, currentApiFile, stubPackages); if (generateDocs && apiFile != null) { ClearPage.copyFile(currentApiFile, new File(apiFile)); } // Reference documentation if (generateDocs) { ClearPage.addBundledTemplateDir("assets/customizations"); ClearPage.addBundledTemplateDir("assets/templates"); List<ResourceLoader> resourceLoaders = new ArrayList<ResourceLoader>(); List<String> templates = ClearPage.getTemplateDirs(); for (String tmpl : templates) { resourceLoaders.add(new FileSystemResourceLoader(tmpl)); } templates = ClearPage.getBundledTemplateDirs(); for (String tmpl : templates) { resourceLoaders.add(new ClassResourceLoader(Doclava.class, '/' + tmpl)); } ResourceLoader compositeResourceLoader = new CompositeResourceLoader(resourceLoaders); jSilver = new JSilver(compositeResourceLoader); if (!Doclava.readTemplateSettings()) { return false; } long startTime = System.nanoTime(); // Use current version information if (apiVersion != null && sinceTagger.hasVersions()) { sinceTagger.addVersion(currentApiFile.getAbsolutePath(), apiVersion); } // 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); } writeAssets(); // Navigation tree NavTree.writeNavTree(assetsOutputDir); // Write Markdown files writeMarkdowns(); // Mule writeModules(muleXmlDir + "modules" + htmlExtension); // Packages Pages writePackages(javadocDir + "packages" + htmlExtension); // Classes writeClassLists(); writeClasses(); writeHierarchy(); // writeKeywords(); // Lists for JavaScript writeLists(); if (keepListFile != null) { writeKeepList(keepListFile); } // Index page writeIndex(); // Installation page writeInstall(); Proofread.finishProofread(proofreadFile); long time = System.nanoTime() - startTime; System.out.println("Mule DevKit took " + (time / 1000000000) + " sec. to write docs to " + ClearPage.outputDir); } Errors.printErrors(); return !Errors.hadError; } private static void writeIndex() { Data data = makeHDF(); ClearPage.write(data, "index.cs", "index" + htmlExtension); } private static void writeInstall() { Data data = makeHDF(); ClearPage.write(data, "install.cs", guideDir + "install" + 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<String> knownTags, ArrayList<String> knownTagsFiles) { for (String fn : knownTagsFiles) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(fn)); int lineno = 0; boolean fail = false; while (true) { lineno++; String line = in.readLine(); if (line == null) { break; } line = line.trim(); if (line.length() == 0) { continue; } else if (line.charAt(0) == '#') { continue; } String[] words = line.split("\\s+", 2); if (words.length == 2) { if (words[1].charAt(0) != '#') { System.err.println(fn + ":" + lineno + ": Only one tag allowed per line: " + line); fail = true; continue; } } knownTags.add(words[0]); } if (fail) { return false; } } catch (IOException ex) { System.err.println("Error reading file: " + fn + " (" + ex.getMessage() + ")"); return false; } finally { if (in != null) { try { in.close(); } catch (IOException ignored) { } } } } 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("-markdown")) { return 4; } if (option.equals("-knowntags")) { return 2; } if (option.equals("-toroot")) { return 2; } if (option.equals("-samplecode")) { return 4; } if (option.equals("-title")) { return 2; } if (option.equals("-werror")) { return 1; } if (option.equals("-hide")) { return 2; } if (option.equals("-warning")) { return 2; } if (option.equals("-error")) { return 2; } if (option.equals("-keeplist")) { return 2; } if (option.equals("-proofread")) { return 2; } if (option.equals("-todo")) { return 2; } if (option.equals("-public")) { return 1; } if (option.equals("-protected")) { return 1; } if (option.equals("-package")) { return 1; } if (option.equals("-private")) { return 1; } if (option.equals("-hidden")) { return 1; } if (option.equals("-stubs")) { return 2; } if (option.equals("-stubpackages")) { return 2; } if (option.equals("-sdkvalues")) { return 2; } if (option.equals("-apixml")) { return 2; } if (option.equals("-nodocs")) { return 1; } if (option.equals("-parsecomments")) { return 1; } if (option.equals("-since")) { return 3; } if (option.equals("-offlinemode")) { return 1; } if (option.equals("-federate")) { return 3; } if (option.equals("-federationxml")) { return 3; } if (option.equals("-apiversion")) { return 2; } if (option.equals("-assetsdir")) { return 2; } if (option.equals("-generatesources")) { 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 Data makeHDF() { Data data = jSilver.createData(); for (String[] p : mHDFData) { data.setValue(p[0], p[1]); } data.setValue("tabs.0.id", "guide"); data.setValue("tabs.0.title", "Install Guide"); data.setValue("tabs.0.link", guideDir + "install.html"); int i = 0; for (String[] p : mMarkdown) { i++; data.setValue("tabs." + i + ".id", p[0]); data.setValue("tabs." + i + ".title", p[2]); String outFile = FilenameUtils.getName(p[1]).replaceAll(".md", ".html").toLowerCase(); data.setValue("tabs." + i + ".link", outFile); } i++; data.setValue("tabs." + i + ".id", "java"); data.setValue("tabs." + i + ".title", "Java API Reference"); data.setValue("tabs." + i + ".link", javadocDir + "packages.html"); boolean hasModules = false; ClassInfo[] classes = Converter.rootClasses(); for (ClassInfo cl : classes) { if( cl.isModule() ) { hasModules = true; } } if( hasModules ) { i++; data.setValue("tabs." + i + ".id", "mule"); data.setValue("tabs." + i + ".title", "Mule API Reference"); data.setValue("tabs." + i + ".link", muleXmlDir + "modules.html"); } return data; } public static Data makePackageHDF() { Data 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.getInterfaces(); 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", "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.key", SinceTagger.keyForName(pkg.getSince())); data.setValue("docs.packages." + i + ".since.name", pkg.getSince()); TagInfo.makeHDF(data, "docs.packages." + i + ".shortDescr", pkg.firstSentenceTags()); i++; } sinceTagger.writeVersionNames(data); return data; } private static void writeDirectory(Map<String, List<TocInfo>> toc, File dir, String relative, JSilver js, String out) { File[] files = dir.listFiles(); int i, count = files.length; for (i = 0; i < count; i++) { File f = files[i]; if (f.isFile()) { String templ = ensureSlash(relative) + f.getName(); int len = templ.length(); /* if (len > 3 && ".cs".equals(templ.substring(len - 3))) { Data data = makeHDF(); makeTocHDF(toc, data); String filename = out + templ.substring(0, len - 3) + htmlExtension; ClearPage.write(data, templ, filename, js); */ if (len > 3 && ".jd".equals(templ.substring(len - 3))) { String filename = out + templ.substring(0, len - 3) + htmlExtension; DocFile.writePage(toc, f.getAbsolutePath(), relative, filename); } else { ClearPage.copyFile(f, new File(ensureSlash(out) + templ)); } } else if (f.isDirectory()) { writeDirectory(toc, f, ensureSlash(relative) + f.getName() + "/", js, out); } } } public static void writeAssets() { JarFile thisJar = JarUtils.jarForClass(Doclava.class, null); if (thisJar != null) { try { List<String> templateDirs = ClearPage.getBundledTemplateDirs(); for (String templateDir : templateDirs) { String assetsDir = ensureSlash(templateDir) + "assets"; JarUtils.copyResourcesToDirectory(thisJar, assetsDir, ensureSlash(ClearPage.outputDir) + assetsOutputDir); } // write mule-developer-core.css Data data = makeHDF(); ClearPage.write(data, "mule-developer-core.cs", assetsOutputDir + "/mule-developer-core.css"); } catch (IOException e) { System.err.println("Error copying assets directory."); e.printStackTrace(); return; } } List<String> templateDirs = ClearPage.getTemplateDirs(); for (String templateDir : templateDirs) { File assets = new File(ensureSlash(templateDir) + "assets"); if (assets.isDirectory()) { writeDirectory(null, assets, assetsOutputDir, null, ""); } } } public static void writeLists() { Data 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); for (MethodInfo method : cl.methods()) { if (method.isProcessor() || method.isSource() || method.isTransformer()) { sorted.put(method.elementName(), method); } } } 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", "java/" + pkg.htmlPage()); data.setValue("docs.pages." + i + ".type", "package"); } else if (o instanceof ClassInfo) { ClassInfo cl = (ClassInfo) o; data.setValue("docs.pages." + i + ".link", "java/" + cl.htmlPage()); data.setValue("docs.pages." + i + ".type", "class"); } else if (o instanceof MethodInfo) { MethodInfo mi = (MethodInfo) o; data.setValue("docs.pages." + i + ".id", "" + i); data.setValue("docs.pages." + i + ".link", "mule/" + mi.relativeModulePath()); data.setValue("docs.pages." + i + ".type", "method"); } 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.getInterfaces()) { 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<PackageInfo>(); 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.getInterfaces(); 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 PackageInfo[] chooseModulePackages() { ClassInfo[] classes = Converter.rootClasses(); SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>(); for (ClassInfo cl : classes) { if (!cl.isModule()) { continue; } PackageInfo pkg = cl.containingPackage(); String name; if (pkg == null) { name = ""; } else { name = pkg.name(); } sorted.put(name, pkg); } ArrayList<PackageInfo> result = new ArrayList<PackageInfo>(); for (String s : sorted.keySet()) { PackageInfo pkg = sorted.get(s); result.add(pkg); } return result.toArray(new PackageInfo[result.size()]); } 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 writeMarkdowns() { MarkdownProcessor markdown = new MarkdownProcessor(); for (String[] m : mMarkdown) { try { String mdContent = FileUtils.readFileToString(new File(m[1])); String htmlContent = markdown.markdown(mdContent); String outFile = FilenameUtils.getName(m[1]).replaceAll(".md", ".html").toLowerCase(); Data data = makeHDF(); data.setValue("content", htmlContent); data.setValue("section", m[0]); ClearPage.write(data, "markdown.cs", outFile); } catch (IOException e) { System.err.println("Cannot read " + m[1] + " file: " + e.getMessage()); } } } public static void writeModules(String filename) { Data data = makeHDF(); int i = 0; for (PackageInfo pkg : chooseModulePackages()) { data.setValue("reference", "1"); data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0"); data.setValue("docs.packages." + i + ".name", pkg.name()); makeModuleListHDF(data, "docs.packages." + i + ".modules", pkg.modules()); for (int j = 0; j < pkg.modules().length; j++) { Data classData = makeHDF(); ClassInfo mod = pkg.modules()[j]; writeModule(mod, classData); } i++; } setPageTitle(data, "Module Index"); TagInfo.makeHDF(data, "root.descr", Converter.convertTags(root.inlineTags(), null)); ClearPage.write(data, "modules.cs", filename); Proofread.writePackages(filename, Converter.convertTags(root.inlineTags(), null)); } public static void writeModule(ClassInfo cl, Data data) { cl.makeHDF(data); setPageTitle(data, cl.name()); ClearPage.write(data, "module.cs", Doclava.muleXmlDir + cl.modulePath()); ClearPage.write(data, "schema.cs", Doclava.muleXmlDir + cl.moduleSchemaPath()); //Proofread.writeClass(cl.modulePath(), cl); } 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.key", SinceTagger.keyForName(pkg.getSince())); data.setValue("package.since.name", pkg.getSince()); data.setValue("package.descr", "...description..."); pkg.setFederatedReferences(data, "package"); makeClassListHDF(data, "package.annotations", ClassInfo.sortByName(pkg.getAnnotations())); makeClassListHDF(data, "package.interfaces", ClassInfo.sortByName(pkg.getInterfaces())); makeClassListHDF(data, "package.classes", ClassInfo.sortByName(pkg.ordinaryClasses())); makeClassListHDF(data, "package.modules", ClassInfo.sortByName(pkg.modules())); 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[] shortDescrTags = pkg.firstSentenceTags(); TagInfo[] longDescrTags = pkg.inlineTags(); TagInfo.makeHDF(data, "package.shortDescr", shortDescrTags); TagInfo.makeHDF(data, "package.descr", longDescrTags); data.setValue("package.hasLongDescr", TagInfo.tagsEqual(shortDescrTags, longDescrTags) ? "0" : "1"); String filename = Doclava.javadocDir + pkg.relativePath(); setPageTitle(data, name); ClearPage.write(data, "package.cs", filename); filename = javadocDir + pkg.fullDescriptionFile(); setPageTitle(data, name + " Details"); ClearPage.write(data, "package-descr.cs", filename); Proofread.writePackage(filename, pkg.inlineTags()); } public static void writeClassLists() { int i; Data 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); } } 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(); if (generateSources) { mHDFData.add(new String[]{"doclava.generate.sources", "true"}); for (ClassInfo cl : classes) { Data data = makePackageHDF(); if (!cl.isHidden()) { writeSource(cl, data); writeClass(cl, data); } } } else { for (ClassInfo cl : classes) { Data data = makePackageHDF(); if (!cl.isHidden()) { writeClass(cl, data); } } } } public static void writeClass(ClassInfo cl, Data data) { cl.makeHDF(data); setPageTitle(data, cl.name()); ClearPage.write(data, "class.cs", Doclava.javadocDir + cl.relativePath()); Proofread.writeClass(cl.htmlPage(), cl); } public static void writeSource(ClassInfo cl, Data data) { try { cl.makeHDF(data); data.setValue("class.source", SampleTagInfo.escapeHtml(cl.getSource())); setPageTitle(data, cl.name()); ClearPage.write(data, "source.cs", Doclava.javadocDir + cl.relativePath("-source")); } catch (IOException e) { Errors.error(Errors.IO_ERROR, null, "Could not find source file for " + cl.name()); } } public static void makeClassListHDF(Data 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 void makeModuleListHDF(Data data, String base, ClassInfo[] classes) { for (int i = 0; i < classes.length; i++) { ClassInfo cl = classes[i]; if (!cl.isHidden()) { cl.makeModuleShortDescrHDF(data, base + "." + i); } } } public static String linkTarget(String source, String target) { String[] src = source.split("/"); String[] tgt = target.split("/"); int srclen = src.length; int tgtlen = tgt.length; int same = 0; while (same < (srclen - 1) && same < (tgtlen - 1) && (src[same].equals(tgt[same]))) { same++; } String s = ""; int up = srclen - same - 1; for (int i = 0; i < up; i++) { s += "../"; } int N = tgtlen - 1; for (int i = same; i < N; i++) { s += tgt[i] + '/'; } s += tgt[tgtlen - 1]; return s; } /** * Returns true if the given element has an @hide or @pending annotation. */ private static boolean hasHideAnnotation(Doc doc) { String comment = doc.getRawCommentText(); return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1; } /** * Returns true if the given element is hidden. */ private static boolean isHidden(Doc doc) { // Methods, fields, constructors. if (doc instanceof MemberDoc) { return hasHideAnnotation(doc); } // Classes, interfaces, enums, annotation types. if (doc instanceof ClassDoc) { ClassDoc classDoc = (ClassDoc) doc; // Check the containing package. if (hasHideAnnotation(classDoc.containingPackage())) { return true; } // Check the class doc and containing class docs if this is a // nested class. ClassDoc current = classDoc; do { if (hasHideAnnotation(current)) { return true; } current = current.containingClass(); } while (current != null); } return false; } /** * Filters out hidden elements. */ private static Object filterHidden(Object o, Class<?> expected) { if (o == null) { return null; } Class<?> type = o.getClass(); if (type.getName().startsWith("com.sun.")) { // TODO: Implement interfaces from superclasses, too. return Proxy .newProxyInstance(type.getClassLoader(), type.getInterfaces(), new HideHandler(o)); } else if (o instanceof Object[]) { Class<?> componentType = expected.getComponentType(); Object[] array = (Object[]) o; List<Object> list = new ArrayList<Object>(array.length); for (Object entry : array) { if ((entry instanceof Doc) && isHidden((Doc) entry)) { continue; } list.add(filterHidden(entry, componentType)); } return list.toArray((Object[]) Array.newInstance(componentType, list.size())); } else { return o; } } /** * Filters hidden elements out of method return values. */ private static class HideHandler implements InvocationHandler { private final Object target; public HideHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (args != null) { if (methodName.equals("compareTo") || methodName.equals("equals") || methodName.equals("overrides") || methodName.equals("subclassOf")) { args[0] = unwrap(args[0]); } } if (methodName.equals("getRawCommentText")) { return filterComment((String) method.invoke(target, args)); } // escape "&" in disjunctive types. if (proxy instanceof Type && methodName.equals("toString")) { return ((String) method.invoke(target, args)).replace("&", "&"); } try { return filterHidden(method.invoke(target, args), method.getReturnType()); } catch (InvocationTargetException e) { throw e.getTargetException(); } } private String filterComment(String s) { if (s == null) { return null; } s = s.trim(); // Work around off by one error while (s.length() >= 5 && s.charAt(s.length() - 5) == '{') { s += " "; } return s; } private static Object unwrap(Object proxy) { if (proxy instanceof Proxy) { return ((HideHandler) Proxy.getInvocationHandler(proxy)).target; } return proxy; } } /** * Ensures a trailing '/' at the end of a string. */ static String ensureSlash(String path) { return path.endsWith("/") ? path : path + "/"; } }