| /* |
| * Copyright 2006 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.doctool; |
| |
| import com.google.doctool.LinkResolver.ExtraClassResolver; |
| |
| import com.sun.javadoc.ClassDoc; |
| import com.sun.javadoc.ConstructorDoc; |
| import com.sun.javadoc.Doc; |
| import com.sun.javadoc.DocErrorReporter; |
| import com.sun.javadoc.ExecutableMemberDoc; |
| import com.sun.javadoc.FieldDoc; |
| import com.sun.javadoc.MemberDoc; |
| import com.sun.javadoc.MethodDoc; |
| import com.sun.javadoc.PackageDoc; |
| import com.sun.javadoc.ParamTag; |
| import com.sun.javadoc.Parameter; |
| import com.sun.javadoc.ProgramElementDoc; |
| import com.sun.javadoc.RootDoc; |
| import com.sun.javadoc.SeeTag; |
| import com.sun.javadoc.SourcePosition; |
| import com.sun.javadoc.Tag; |
| import com.sun.javadoc.Type; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Stack; |
| |
| /** |
| * Generates XML from Javadoc source, with particular idioms to make it possible |
| * to translate into either expository doc or API doc. |
| */ |
| public class Booklet { |
| |
| private static final String OPT_BKCODE = "-bkcode"; |
| private static final String OPT_BKDOCPKG = "-bkdocpkg"; |
| private static final String OPT_BKOUT = "-bkout"; |
| |
| private static Booklet sBooklet; |
| |
| public static void main(String[] args) { |
| // Strip off our arguments at the beginning. |
| // |
| com.sun.tools.javadoc.Main.execute(args); |
| } |
| |
| public static int optionLength(String option) { |
| if (option.equals(OPT_BKOUT)) { |
| return 2; |
| } else if (option.equals(OPT_BKDOCPKG)) { |
| return 2; |
| } else if (option.equals(OPT_BKCODE)) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| public static String slurpSource(SourcePosition position) { |
| BufferedReader br = null; |
| try { |
| br = new BufferedReader(new FileReader(position.file())); |
| for (int i = 0, n = position.line() - 1; i < n; ++i) { |
| br.readLine(); |
| } |
| |
| StringBuffer lines = new StringBuffer(); |
| String line = br.readLine(); |
| int braceDepth = 0; |
| int indent = -1; |
| boolean seenSemiColonOrBrace = false; |
| while (line != null) { |
| if (indent == -1) { |
| for (indent = 0; Character.isWhitespace(line.charAt(indent)); ++indent) { |
| // just accumulate |
| } |
| } |
| |
| if (line.length() >= indent) { |
| line = line.substring(indent); |
| } |
| |
| lines.append(line + "\n"); |
| for (int i = 0, n = line.length(); i < n; ++i) { |
| char c = line.charAt(i); |
| if (c == '{') { |
| seenSemiColonOrBrace = true; |
| ++braceDepth; |
| } else if (c == '}') { |
| --braceDepth; |
| } else if (c == ';') { |
| seenSemiColonOrBrace = true; |
| } |
| } |
| |
| if (braceDepth > 0 || !seenSemiColonOrBrace) { |
| line = br.readLine(); |
| } else { |
| break; |
| } |
| } |
| |
| String code = lines.toString(); |
| return code; |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } finally { |
| try { |
| if (br != null) { |
| br.close(); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| return ""; |
| } |
| |
| public static boolean start(RootDoc rootDoc) { |
| getBooklet().process(rootDoc); |
| return true; |
| } |
| |
| public static boolean validOptions(String[][] options, |
| DocErrorReporter reporter) { |
| return getBooklet().analyzeOptions(options, reporter); |
| } |
| |
| private static Booklet getBooklet() { |
| if (sBooklet == null) { |
| sBooklet = new Booklet(); |
| } |
| return sBooklet; |
| } |
| |
| private String outputPath; |
| |
| private HashSet packagesToGenerate; |
| |
| private RootDoc initialRootDoc; |
| |
| private String rootDocId; |
| |
| private boolean showCode; |
| |
| private HashSet standardTagKinds = new HashSet(); |
| |
| private Stack tagStack = new Stack(); |
| |
| private PrintWriter pw; |
| |
| public Booklet() { |
| // Set up standard tags (to ignore during tag processing) |
| // |
| standardTagKinds.add("@see"); |
| standardTagKinds.add("@serial"); |
| standardTagKinds.add("@throws"); |
| standardTagKinds.add("@param"); |
| standardTagKinds.add("@id"); |
| } |
| |
| private boolean analyzeOptions(String[][] options, DocErrorReporter reporter) { |
| for (int i = 0, n = options.length; i < n; ++i) { |
| if (options[i][0].equals(OPT_BKOUT)) { |
| outputPath = options[i][1]; |
| } else if (options[i][0].equals(OPT_BKDOCPKG)) { |
| String[] packages = options[i][1].split(";"); |
| packagesToGenerate = new HashSet(); |
| for (int packageIndex = 0; packageIndex < packages.length; ++packageIndex) { |
| packagesToGenerate.add(packages[packageIndex]); |
| } |
| } else if (options[i][0].equals(OPT_BKCODE)) { |
| showCode = true; |
| } |
| } |
| |
| if (outputPath == null) { |
| reporter.printError("You must specify an output directory with " |
| + OPT_BKOUT); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private void begin(String tag) { |
| pw.print("<" + tag + ">"); |
| tagStack.push(tag); |
| } |
| |
| private void begin(String tag, String attr, String value) { |
| pw.print("<" + tag + " " + attr + "='" + value + "'>"); |
| tagStack.push(tag); |
| } |
| |
| private void beginCDATA() { |
| pw.print("<![CDATA["); |
| } |
| |
| private void beginEndln(String tag) { |
| pw.println("<" + tag + "/>"); |
| } |
| |
| private void beginln(String tag) { |
| pw.println(); |
| begin(tag); |
| } |
| |
| private void beginln(String tag, String attr, String value) { |
| pw.println(); |
| begin(tag, attr, value); |
| } |
| |
| private void emitDescription(ClassDoc enclosing, Doc forWhat, |
| Tag[] leadInline, Tag[] descInline) { |
| emitJRELink(enclosing, forWhat); |
| |
| beginln("lead"); |
| processTags(leadInline); |
| endln(); |
| |
| beginln("description"); |
| processTags(descInline); |
| endln(); |
| } |
| |
| private void emitIdentity(String id, String name) { |
| beginln("id"); |
| text(id); |
| endln(); |
| |
| beginln("name"); |
| text(name); |
| endln(); |
| } |
| |
| private void emitJRELink(ClassDoc enclosing, Doc doc) { |
| String jreLink = "http://java.sun.com/j2se/1.5.0/docs/api/"; |
| if (doc instanceof ClassDoc) { |
| ClassDoc classDoc = (ClassDoc) doc; |
| String pkg = classDoc.containingPackage().name(); |
| if (!pkg.startsWith("java.")) { |
| return; |
| } |
| String clazz = classDoc.name(); |
| |
| jreLink += pkg.replace('.', '/') + "/"; |
| jreLink += clazz; |
| jreLink += ".html"; |
| } else if (doc instanceof ExecutableMemberDoc) { |
| ExecutableMemberDoc execMemberDoc = (ExecutableMemberDoc) doc; |
| String pkg = enclosing.containingPackage().name(); |
| if (!pkg.startsWith("java.")) { |
| return; |
| } |
| String clazz = enclosing.name(); |
| String method = execMemberDoc.name(); |
| String sig = execMemberDoc.signature(); |
| |
| jreLink += pkg.replace('.', '/') + "/"; |
| jreLink += clazz; |
| jreLink += ".html"; |
| jreLink += "#"; |
| jreLink += method; |
| jreLink += sig; |
| } else if (doc instanceof PackageDoc) { |
| String pkg = doc.name(); |
| if (!pkg.startsWith("java.")) { |
| return; |
| } |
| jreLink += pkg.replace('.', '/') + "/package-summary.html"; |
| } else if (doc instanceof FieldDoc) { |
| FieldDoc fieldDoc = (FieldDoc) doc; |
| String pkg = enclosing.containingPackage().name(); |
| if (!pkg.startsWith("java.")) { |
| return; |
| } |
| String clazz = fieldDoc.containingClass().name(); |
| String field = fieldDoc.name(); |
| |
| jreLink += pkg.replace('.', '/') + "/"; |
| jreLink += clazz; |
| jreLink += ".html"; |
| jreLink += "#"; |
| jreLink += field; |
| } |
| |
| // Add the link. |
| // |
| beginln("jre"); |
| text(jreLink); |
| endln(); |
| } |
| |
| private void emitLocation(Doc doc) { |
| Doc parent = getParentDoc(doc); |
| if (parent != null) { |
| beginln("location"); |
| emitLocationLink(parent); |
| endln(); |
| } |
| } |
| |
| private void emitLocationLink(Doc doc) { |
| // Intentionally reverses the order. |
| // |
| String myId; |
| String myTitle; |
| if (doc instanceof MemberDoc) { |
| MemberDoc memberDoc = (MemberDoc) doc; |
| myId = getId(memberDoc); |
| myTitle = memberDoc.name(); |
| } else if (doc instanceof ClassDoc) { |
| ClassDoc classDoc = (ClassDoc) doc; |
| myId = getId(classDoc); |
| myTitle = classDoc.name(); |
| } else if (doc instanceof PackageDoc) { |
| PackageDoc pkgDoc = (PackageDoc) doc; |
| myId = getId(pkgDoc); |
| myTitle = pkgDoc.name(); |
| } else if (doc instanceof RootDoc) { |
| myId = rootDocId; |
| myTitle = initialRootDoc.name(); |
| } else { |
| throw new IllegalStateException( |
| "Expected only a member, type, or package"); |
| } |
| |
| Doc parent = getParentDoc(doc); |
| if (parent != null) { |
| emitLocationLink(parent); |
| } |
| |
| beginln("link", "ref", myId); |
| |
| Tag[] titleTag = doc.tags("@title"); |
| if (titleTag.length > 0) { |
| myTitle = titleTag[0].text().trim(); |
| } |
| |
| if (myTitle == null || myTitle.length() == 0) { |
| myTitle = "[NO TITLE]"; |
| } |
| |
| text(myTitle); |
| |
| endln(); |
| } |
| |
| private void emitModifiers(ProgramElementDoc doc) { |
| if (doc.isPrivate()) { |
| beginEndln("isPrivate"); |
| } else if (doc.isProtected()) { |
| beginEndln("isProtected"); |
| } else if (doc.isPublic()) { |
| beginEndln("isPublic"); |
| } else if (doc.isPackagePrivate()) { |
| beginEndln("isPackagePrivate"); |
| } |
| |
| if (doc.isStatic()) { |
| beginEndln("isStatic"); |
| } |
| |
| if (doc.isFinal()) { |
| beginEndln("isFinal"); |
| } |
| |
| if (doc instanceof MethodDoc) { |
| MethodDoc methodDoc = (MethodDoc) doc; |
| |
| if (methodDoc.isAbstract()) { |
| beginEndln("isAbstract"); |
| } |
| |
| if (methodDoc.isSynchronized()) { |
| beginEndln("isSynchronized"); |
| } |
| } |
| } |
| |
| private void emitOutOfLineTags(Tag[] tags) { |
| beginln("tags"); |
| processTags(tags); |
| endln(); |
| } |
| |
| private void emitType(Type type) { |
| ClassDoc typeAsClass = type.asClassDoc(); |
| |
| if (typeAsClass != null) { |
| begin("type", "ref", getId(typeAsClass)); |
| } else { |
| begin("type"); |
| } |
| |
| String typeName = type.typeName(); |
| String dims = type.dimension(); |
| |
| text(typeName + dims); |
| |
| end(); |
| } |
| |
| private void end() { |
| pw.print("</" + tagStack.pop() + ">"); |
| } |
| |
| private void endCDATA() { |
| pw.print("]]>"); |
| } |
| |
| private void endln() { |
| end(); |
| pw.println(); |
| } |
| |
| private MethodDoc findMatchingInterfaceMethodDoc(ClassDoc[] interfaces, |
| MethodDoc methodDoc) { |
| if (interfaces != null) { |
| // Look through the methods on superInterface for a matching methodDoc. |
| // |
| for (int intfIndex = 0; intfIndex < interfaces.length; ++intfIndex) { |
| ClassDoc currentIntfDoc = interfaces[intfIndex]; |
| MethodDoc[] intfMethodDocs = currentIntfDoc.methods(); |
| for (int methodIndex = 0; methodIndex < intfMethodDocs.length; ++methodIndex) { |
| MethodDoc intfMethodDoc = intfMethodDocs[methodIndex]; |
| String methodDocName = methodDoc.name(); |
| String intfMethodDocName = intfMethodDoc.name(); |
| if (methodDocName.equals(intfMethodDocName)) { |
| if (methodDoc.signature().equals(intfMethodDoc.signature())) { |
| // It's a match! |
| // |
| return intfMethodDoc; |
| } |
| } |
| } |
| |
| // Try the superinterfaces of this interface. |
| // |
| MethodDoc foundMethodDoc = findMatchingInterfaceMethodDoc( |
| currentIntfDoc.interfaces(), methodDoc); |
| if (foundMethodDoc != null) { |
| return foundMethodDoc; |
| } |
| } |
| } |
| |
| // Just didn't find it anywhere. Must not be based on an implemented |
| // interface. |
| // |
| return null; |
| } |
| |
| private ExtraClassResolver getExtraClassResolver(Tag tag) { |
| |
| if (tag.holder() instanceof PackageDoc) { |
| return new ExtraClassResolver() { |
| public ClassDoc findClass(String className) { |
| return initialRootDoc.classNamed(className); |
| } |
| }; |
| } |
| |
| return null; |
| } |
| |
| private String getId(ClassDoc classDoc) { |
| return classDoc.qualifiedName(); |
| } |
| |
| private String getId(ExecutableMemberDoc memberDoc) { |
| // Use the mangled name to look up a unique id (based on its hashCode). |
| // |
| String clazz = memberDoc.containingClass().qualifiedName(); |
| String id = clazz + "#" + memberDoc.name() + memberDoc.signature(); |
| return id; |
| } |
| |
| private String getId(FieldDoc fieldDoc) { |
| String clazz = fieldDoc.containingClass().qualifiedName(); |
| String id = clazz + "#" + fieldDoc.name(); |
| return id; |
| } |
| |
| private String getId(MemberDoc memberDoc) { |
| if (memberDoc.isMethod()) { |
| return getId((MethodDoc) memberDoc); |
| } else if (memberDoc.isConstructor()) { |
| return getId((ConstructorDoc) memberDoc); |
| } else if (memberDoc.isField()) { |
| return getId((FieldDoc) memberDoc); |
| } else { |
| throw new RuntimeException("Unknown member type"); |
| } |
| } |
| |
| private String getId(PackageDoc packageDoc) { |
| return packageDoc.name(); |
| } |
| |
| private Doc getParentDoc(Doc doc) { |
| if (doc instanceof MemberDoc) { |
| MemberDoc memberDoc = (MemberDoc) doc; |
| return memberDoc.containingClass(); |
| } else if (doc instanceof ClassDoc) { |
| ClassDoc classDoc = (ClassDoc) doc; |
| Doc enclosingClass = classDoc.containingClass(); |
| if (enclosingClass != null) { |
| return enclosingClass; |
| } else { |
| return classDoc.containingPackage(); |
| } |
| } else if (doc instanceof PackageDoc) { |
| return initialRootDoc; |
| } else if (doc instanceof RootDoc) { |
| return null; |
| } else { |
| throw new IllegalStateException( |
| "Expected only a member, type, or package"); |
| } |
| } |
| |
| private boolean looksSynthesized(ExecutableMemberDoc memberDoc) { |
| SourcePosition memberPos = memberDoc.position(); |
| int memberLine = memberPos.line(); |
| |
| SourcePosition classPos = memberDoc.containingClass().position(); |
| int classLine = classPos.line(); |
| |
| if (memberLine == classLine) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| private void process(ClassDoc enclosing, ClassDoc classDoc) { |
| // Make sure it isn't a @skip-ped topic. |
| // |
| if (classDoc.tags("@skip").length > 0) { |
| // This one is explicitly skipped right now. |
| // |
| return; |
| } |
| |
| if (classDoc.isInterface()) { |
| beginln("interface"); |
| } else { |
| beginln("class"); |
| } |
| |
| emitIdentity(getId(classDoc), classDoc.name()); |
| emitLocation(classDoc); |
| emitDescription(enclosing, classDoc, classDoc.firstSentenceTags(), |
| classDoc.inlineTags()); |
| emitOutOfLineTags(classDoc.tags()); |
| emitModifiers(classDoc); |
| |
| ClassDoc superclassDoc = classDoc.superclass(); |
| if (superclassDoc != null) { |
| beginln("superclass", "ref", getId(superclassDoc)); |
| text(superclassDoc.name()); |
| endln(); |
| } |
| |
| ClassDoc[] superinterfacesDoc = classDoc.interfaces(); |
| for (int i = 0; i < superinterfacesDoc.length; i++) { |
| ClassDoc superinterfaceDoc = superinterfacesDoc[i]; |
| beginln("superinterface", "ref", getId(superinterfaceDoc)); |
| text(superinterfaceDoc.name()); |
| endln(); |
| } |
| |
| ClassDoc[] cda = classDoc.innerClasses(); |
| for (int i = 0; i < cda.length; i++) { |
| process(classDoc, cda[i]); |
| } |
| |
| FieldDoc[] fda = classDoc.fields(); |
| for (int i = 0; i < fda.length; i++) { |
| process(classDoc, fda[i]); |
| } |
| |
| ConstructorDoc[] ctorDocs = classDoc.constructors(); |
| for (int i = 0; i < ctorDocs.length; i++) { |
| process(classDoc, ctorDocs[i]); |
| } |
| |
| MethodDoc[] methods = classDoc.methods(); |
| for (int i = 0; i < methods.length; i++) { |
| process(classDoc, methods[i]); |
| } |
| |
| endln(); |
| } |
| |
| private void process(ClassDoc enclosing, ExecutableMemberDoc memberDoc) { |
| if (looksSynthesized(memberDoc)) { |
| // Skip it. |
| // |
| return; |
| } |
| |
| // Make sure it isn't a @skip-ped member. |
| // |
| if (memberDoc.tags("@skip").length > 0) { |
| // This one is explicitly skipped right now. |
| // |
| return; |
| } |
| |
| if (memberDoc instanceof MethodDoc) { |
| beginln("method"); |
| emitIdentity(getId(memberDoc), memberDoc.name()); |
| emitLocation(memberDoc); |
| |
| // If this method is not explicitly documented, use the best inherited |
| // one. |
| // |
| String rawComment = memberDoc.getRawCommentText(); |
| if (rawComment.length() == 0) { |
| // Switch out the member doc being used. |
| // |
| MethodDoc methodDoc = (MethodDoc) memberDoc; |
| MethodDoc superMethodDoc = methodDoc.overriddenMethod(); |
| |
| if (superMethodDoc == null) { |
| |
| ClassDoc classDocToTry = methodDoc.containingClass(); |
| while (classDocToTry != null) { |
| // See if this is a method from an interface. |
| // If so, borrow its description. |
| // |
| superMethodDoc = findMatchingInterfaceMethodDoc( |
| classDocToTry.interfaces(), methodDoc); |
| |
| if (superMethodDoc != null) { |
| break; |
| } |
| |
| classDocToTry = classDocToTry.superclass(); |
| } |
| } |
| |
| if (superMethodDoc != null) { |
| // Borrow the description from the superclass/superinterface. |
| // |
| memberDoc = superMethodDoc; |
| } |
| } |
| } else if (memberDoc instanceof ConstructorDoc) { |
| beginln("constructor"); |
| emitIdentity(getId(memberDoc), memberDoc.containingClass().name()); |
| emitLocation(memberDoc); |
| } else { |
| throw new IllegalStateException("What kind of executable member is this?"); |
| } |
| |
| emitDescription(enclosing, memberDoc, memberDoc.firstSentenceTags(), |
| memberDoc.inlineTags()); |
| emitOutOfLineTags(memberDoc.tags()); |
| emitModifiers(memberDoc); |
| |
| begin("flatSignature"); |
| text(memberDoc.flatSignature()); |
| end(); |
| |
| // Return type if it's a method |
| // |
| if (memberDoc instanceof MethodDoc) { |
| emitType(((MethodDoc) memberDoc).returnType()); |
| } |
| |
| // Parameters |
| // |
| beginln("params"); |
| Parameter[] pda = memberDoc.parameters(); |
| for (int i = 0; i < pda.length; i++) { |
| Parameter pd = pda[i]; |
| |
| begin("param"); |
| emitType(pd.type()); |
| begin("name"); |
| text(pd.name()); |
| end(); |
| end(); |
| } |
| endln(); |
| |
| // Exceptions thrown |
| // |
| ClassDoc[] tea = memberDoc.thrownExceptions(); |
| if (tea.length > 0) { |
| beginln("throws"); |
| for (int i = 0; i < tea.length; ++i) { |
| ClassDoc te = tea[i]; |
| beginln("throw", "ref", getId(te)); |
| text(te.name()); |
| endln(); |
| } |
| endln(); |
| } |
| |
| // Maybe show code |
| // |
| if (showCode) { |
| SourcePosition pos = memberDoc.position(); |
| if (pos != null) { |
| beginln("code"); |
| String source = slurpSource(pos); |
| begin("pre", "class", "code"); |
| beginCDATA(); |
| text(source); |
| endCDATA(); |
| endln(); |
| endln(); |
| } |
| } |
| |
| endln(); |
| } |
| |
| private void process(ClassDoc enclosing, FieldDoc fieldDoc) { |
| // Make sure it isn't @skip-ped. |
| // |
| if (fieldDoc.tags("@skip").length > 0) { |
| // This one is explicitly skipped right now. |
| // |
| return; |
| } |
| |
| String commentText = fieldDoc.commentText(); |
| if (fieldDoc.isPrivate() |
| && (commentText == null || commentText.length() == 0)) { |
| return; |
| } |
| |
| beginln("field"); |
| emitIdentity(fieldDoc.qualifiedName(), fieldDoc.name()); |
| emitLocation(fieldDoc); |
| emitDescription(enclosing, fieldDoc, fieldDoc.firstSentenceTags(), |
| fieldDoc.inlineTags()); |
| emitOutOfLineTags(fieldDoc.tags()); |
| emitModifiers(fieldDoc); |
| emitType(fieldDoc.type()); |
| endln(); |
| } |
| |
| private void process(PackageDoc packageDoc) { |
| beginln("package"); |
| |
| emitIdentity(packageDoc.name(), packageDoc.name()); |
| emitLocation(packageDoc); |
| emitDescription(null, packageDoc, packageDoc.firstSentenceTags(), |
| packageDoc.inlineTags()); |
| emitOutOfLineTags(packageDoc.tags()); |
| |
| // Top-level classes |
| // |
| ClassDoc[] cda = packageDoc.allClasses(); |
| for (int i = 0; i < cda.length; i++) { |
| ClassDoc cd = cda[i]; |
| |
| // Make sure we have source. |
| // |
| SourcePosition p = cd.position(); |
| if (p == null || p.line() == 0) { |
| // Skip this since it isn't ours (otherwise we would have source). |
| // |
| continue; |
| } |
| |
| if (cd.containingClass() == null) { |
| process(cd, cda[i]); |
| } else { |
| // Not a top-level class. |
| // |
| cd = cda[i]; |
| } |
| } |
| |
| endln(); |
| } |
| |
| private void process(RootDoc rootDoc) { |
| try { |
| initialRootDoc = rootDoc; |
| File outputFile = new File(outputPath); |
| // Ignore result since the next line will fail if the directory doesn't |
| // exist. |
| outputFile.getParentFile().mkdirs(); |
| FileWriter fw = new FileWriter(outputFile); |
| pw = new PrintWriter(fw, true); |
| |
| beginln("booklet"); |
| |
| rootDocId = ""; |
| String title = ""; |
| Tag[] idTags = rootDoc.tags("@id"); |
| if (idTags.length > 0) { |
| rootDocId = idTags[0].text(); |
| } else { |
| initialRootDoc.printWarning("Expecting @id in an overview html doc; see -overview"); |
| } |
| |
| Tag[] titleTags = rootDoc.tags("@title"); |
| if (titleTags.length > 0) { |
| title = titleTags[0].text(); |
| } else { |
| initialRootDoc.printWarning("Expecting @title in an overview html doc; see -overview"); |
| } |
| |
| emitIdentity(rootDocId, title); |
| emitLocation(rootDoc); |
| emitDescription(null, rootDoc, rootDoc.firstSentenceTags(), |
| rootDoc.inlineTags()); |
| emitOutOfLineTags(rootDoc.tags()); |
| |
| // Create a list of the packages to iterate over. |
| // |
| HashSet packageNames = new HashSet(); |
| ClassDoc[] cda = initialRootDoc.classes(); |
| for (int i = 0; i < cda.length; i++) { |
| ClassDoc cd = cda[i]; |
| // Only top-level classes matter. |
| // |
| if (cd.containingClass() == null) { |
| packageNames.add(cd.containingPackage().name()); |
| } |
| } |
| |
| // Packages |
| // |
| for (Iterator iter = packageNames.iterator(); iter.hasNext();) { |
| String packageName = (String) iter.next(); |
| |
| // Only process this package if either no "docpkg" is set, or it is |
| // included. |
| // |
| if (packagesToGenerate == null |
| || packagesToGenerate.contains(packageName)) { |
| PackageDoc pd = initialRootDoc.packageNamed(packageName); |
| process(pd); |
| } |
| } |
| |
| endln(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| initialRootDoc.printError("Caught exception: " + e.toString()); |
| } |
| } |
| |
| private void processSeeTag(SeeTag seeTag) { |
| String ref = null; |
| ClassDoc cd = null; |
| PackageDoc pd = null; |
| MemberDoc md = null; |
| String title = null; |
| |
| // Check for HTML links |
| if (seeTag.text().startsWith("<")) { |
| // TODO: ignore for now |
| return; |
| } |
| // Ordered: most-specific to least-specific |
| if (null != (md = seeTag.referencedMember())) { |
| ref = getId(md); |
| } else if (null != (cd = seeTag.referencedClass())) { |
| ref = getId(cd); |
| |
| // See if the target has a title. |
| // |
| Tag[] titleTag = cd.tags("@title"); |
| if (titleTag.length > 0) { |
| title = titleTag[0].text().trim(); |
| if (title.length() == 0) { |
| title = null; |
| } |
| } |
| } else if (null != (pd = seeTag.referencedPackage())) { |
| ref = getId(pd); |
| } |
| |
| String label = seeTag.label(); |
| |
| // If there is a label, use it. |
| if (label == null || label.trim().length() == 0) { |
| |
| // If there isn't a label, see if the @see target has a @title. |
| // |
| if (title != null) { |
| label = title; |
| } else { |
| label = seeTag.text(); |
| |
| if (label.endsWith(".")) { |
| label = label.substring(0, label.length() - 1); |
| } |
| |
| // Rip off all but the last interesting part to prevent fully-qualified |
| // names everywhere. |
| // |
| int last1 = label.lastIndexOf('.'); |
| int last2 = label.lastIndexOf('#'); |
| |
| if (last2 > last1) { |
| // Use the class name plus the member name. |
| // |
| label = label.substring(last1 + 1).replace('#', '.'); |
| } else if (last1 != -1) { |
| label = label.substring(last1 + 1); |
| } |
| |
| if (label.charAt(0) == '.') { |
| // Started with "#" so remove the dot. |
| // |
| label = label.substring(1); |
| } |
| } |
| } |
| |
| if (ref != null) { |
| begin("link", "ref", ref); |
| text(label != null ? label.trim() : ""); |
| end(); |
| } else { |
| initialRootDoc.printWarning(seeTag.position(), |
| "Unverifiable cross-reference to '" + seeTag.text() + "'"); |
| // The link probably won't work, but emit it anyway. |
| begin("link"); |
| text(label != null ? label.trim() : ""); |
| end(); |
| } |
| } |
| |
| private void processTags(Tag[] tags) { |
| for (int i = 0; i < tags.length; i++) { |
| Tag tag = tags[i]; |
| String tagKind = tag.kind(); |
| if (tagKind.equals("Text")) { |
| text(tag.text()); |
| } else if (tagKind.equals("@see")) { |
| processSeeTag((SeeTag) tag); |
| } else if (tagKind.equals("@param")) { |
| ParamTag paramTag = (ParamTag) tag; |
| beginln("param"); |
| begin("name"); |
| text(paramTag.parameterName()); |
| end(); |
| begin("description"); |
| processTags(paramTag.inlineTags()); |
| end(); |
| endln(); |
| } else if (tagKind.equals("@example")) { |
| ExtraClassResolver extraClassResolver = getExtraClassResolver(tag); |
| SourcePosition pos = LinkResolver.resolveLink(tag, extraClassResolver); |
| String source = slurpSource(pos); |
| begin("pre", "class", "code"); |
| beginCDATA(); |
| text(source); |
| endCDATA(); |
| endln(); |
| } else if (tagKind.equals("@gwt.include")) { |
| String contents = ResourceIncluder.getResourceFromClasspathScrubbedForHTML(tag); |
| begin("pre", "class", "code"); |
| text(contents); |
| endln(); |
| } else if (!standardTagKinds.contains(tag.name())) { |
| // Custom tag; pass it along other tag. |
| // |
| String tagName = tag.name().substring(1); |
| begin(tagName); |
| processTags(tag.inlineTags()); |
| end(); |
| } |
| } |
| } |
| |
| private void text(String s) { |
| pw.print(s); |
| } |
| } |