0001: /*
0002: * Copyright 2006 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.doctool;
0017:
0018: import com.google.doctool.LinkResolver.ExtraClassResolver;
0019:
0020: import com.sun.javadoc.ClassDoc;
0021: import com.sun.javadoc.ConstructorDoc;
0022: import com.sun.javadoc.Doc;
0023: import com.sun.javadoc.DocErrorReporter;
0024: import com.sun.javadoc.ExecutableMemberDoc;
0025: import com.sun.javadoc.FieldDoc;
0026: import com.sun.javadoc.MemberDoc;
0027: import com.sun.javadoc.MethodDoc;
0028: import com.sun.javadoc.PackageDoc;
0029: import com.sun.javadoc.ParamTag;
0030: import com.sun.javadoc.Parameter;
0031: import com.sun.javadoc.ProgramElementDoc;
0032: import com.sun.javadoc.RootDoc;
0033: import com.sun.javadoc.SeeTag;
0034: import com.sun.javadoc.SourcePosition;
0035: import com.sun.javadoc.Tag;
0036: import com.sun.javadoc.Type;
0037:
0038: import java.io.BufferedReader;
0039: import java.io.File;
0040: import java.io.FileReader;
0041: import java.io.FileWriter;
0042: import java.io.IOException;
0043: import java.io.PrintWriter;
0044: import java.util.HashSet;
0045: import java.util.Iterator;
0046: import java.util.Stack;
0047:
0048: /**
0049: * Generates XML from Javadoc source, with particular idioms to make it possible
0050: * to translate into either expository doc or API doc.
0051: */
0052: public class Booklet {
0053:
0054: private static final String OPT_BKCODE = "-bkcode";
0055: private static final String OPT_BKDOCPKG = "-bkdocpkg";
0056: private static final String OPT_BKOUT = "-bkout";
0057:
0058: private static Booklet sBooklet;
0059:
0060: public static void main(String[] args) {
0061: // Strip off our arguments at the beginning.
0062: //
0063: com.sun.tools.javadoc.Main.execute(args);
0064: }
0065:
0066: public static int optionLength(String option) {
0067: if (option.equals(OPT_BKOUT)) {
0068: return 2;
0069: } else if (option.equals(OPT_BKDOCPKG)) {
0070: return 2;
0071: } else if (option.equals(OPT_BKCODE)) {
0072: return 1;
0073: }
0074: return 0;
0075: }
0076:
0077: public static String slurpSource(SourcePosition position) {
0078: FileReader fr = null;
0079: try {
0080: fr = new FileReader(position.file());
0081: BufferedReader br = new BufferedReader(fr);
0082: for (int i = 0, n = position.line() - 1; i < n; ++i) {
0083: br.readLine();
0084: }
0085:
0086: StringBuffer lines = new StringBuffer();
0087: String line = br.readLine();
0088: int braceDepth = 0;
0089: int indent = -1;
0090: boolean seenSemiColonOrBrace = false;
0091: while (line != null) {
0092: if (indent == -1) {
0093: for (indent = 0; Character.isWhitespace(line
0094: .charAt(indent)); ++indent) {
0095: // just accumulate
0096: }
0097: }
0098:
0099: if (line.length() >= indent) {
0100: line = line.substring(indent);
0101: }
0102:
0103: lines.append(line + "\n");
0104: for (int i = 0, n = line.length(); i < n; ++i) {
0105: char c = line.charAt(i);
0106: if (c == '{') {
0107: seenSemiColonOrBrace = true;
0108: ++braceDepth;
0109: } else if (c == '}') {
0110: --braceDepth;
0111: } else if (c == ';') {
0112: seenSemiColonOrBrace = true;
0113: }
0114: }
0115:
0116: if (braceDepth > 0 || !seenSemiColonOrBrace) {
0117: line = br.readLine();
0118: } else {
0119: break;
0120: }
0121: }
0122:
0123: String code = lines.toString();
0124: return code;
0125: } catch (Exception e) {
0126: e.printStackTrace();
0127: } finally {
0128: try {
0129: if (fr != null) {
0130: fr.close();
0131: }
0132: } catch (IOException e) {
0133: e.printStackTrace();
0134: }
0135: }
0136: return "";
0137: }
0138:
0139: public static boolean start(RootDoc rootDoc) {
0140: getBooklet().process(rootDoc);
0141: return true;
0142: }
0143:
0144: public static boolean validOptions(String[][] options,
0145: DocErrorReporter reporter) {
0146: return getBooklet().analyzeOptions(options, reporter);
0147: }
0148:
0149: private static Booklet getBooklet() {
0150: if (sBooklet == null) {
0151: sBooklet = new Booklet();
0152: }
0153: return sBooklet;
0154: }
0155:
0156: private String outputPath;
0157:
0158: private HashSet packagesToGenerate;
0159:
0160: private RootDoc initialRootDoc;
0161:
0162: private String rootDocId;
0163:
0164: private boolean showCode;
0165:
0166: private HashSet standardTagKinds = new HashSet();
0167:
0168: private Stack tagStack = new Stack();
0169:
0170: private PrintWriter pw;
0171:
0172: public Booklet() {
0173: // Set up standard tags (to ignore during tag processing)
0174: //
0175: standardTagKinds.add("@see");
0176: standardTagKinds.add("@serial");
0177: standardTagKinds.add("@throws");
0178: standardTagKinds.add("@param");
0179: standardTagKinds.add("@id");
0180: }
0181:
0182: private boolean analyzeOptions(String[][] options,
0183: DocErrorReporter reporter) {
0184: for (int i = 0, n = options.length; i < n; ++i) {
0185: if (options[i][0].equals(OPT_BKOUT)) {
0186: outputPath = options[i][1];
0187: } else if (options[i][0].equals(OPT_BKDOCPKG)) {
0188: String[] packages = options[i][1].split(";");
0189: packagesToGenerate = new HashSet();
0190: for (int packageIndex = 0; packageIndex < packages.length; ++packageIndex) {
0191: packagesToGenerate.add(packages[packageIndex]);
0192: }
0193: } else if (options[i][0].equals(OPT_BKCODE)) {
0194: showCode = true;
0195: }
0196: }
0197:
0198: if (outputPath == null) {
0199: reporter
0200: .printError("You must specify an output directory with "
0201: + OPT_BKOUT);
0202: return false;
0203: }
0204:
0205: return true;
0206: }
0207:
0208: private void begin(String tag) {
0209: pw.print("<" + tag + ">");
0210: tagStack.push(tag);
0211: }
0212:
0213: private void begin(String tag, String attr, String value) {
0214: pw.print("<" + tag + " " + attr + "='" + value + "'>");
0215: tagStack.push(tag);
0216: }
0217:
0218: private void beginCDATA() {
0219: pw.print("<![CDATA[");
0220: }
0221:
0222: private void beginEndln(String tag) {
0223: pw.println("<" + tag + "/>");
0224: }
0225:
0226: private void beginln(String tag) {
0227: pw.println();
0228: begin(tag);
0229: }
0230:
0231: private void beginln(String tag, String attr, String value) {
0232: pw.println();
0233: begin(tag, attr, value);
0234: }
0235:
0236: private void emitDescription(ClassDoc enclosing, Doc forWhat,
0237: Tag[] leadInline, Tag[] descInline) {
0238: emitJRELink(enclosing, forWhat);
0239:
0240: beginln("lead");
0241: processTags(leadInline);
0242: endln();
0243:
0244: beginln("description");
0245: processTags(descInline);
0246: endln();
0247: }
0248:
0249: private void emitIdentity(String id, String name) {
0250: beginln("id");
0251: text(id);
0252: endln();
0253:
0254: beginln("name");
0255: text(name);
0256: endln();
0257: }
0258:
0259: private void emitJRELink(ClassDoc enclosing, Doc doc) {
0260: String jreLink = "http://java.sun.com/j2se/1.5.0/docs/api/";
0261: if (doc instanceof ClassDoc) {
0262: ClassDoc classDoc = (ClassDoc) doc;
0263: String pkg = classDoc.containingPackage().name();
0264: if (!pkg.startsWith("java.")) {
0265: return;
0266: }
0267: String clazz = classDoc.name();
0268:
0269: jreLink += pkg.replace('.', '/') + "/";
0270: jreLink += clazz;
0271: jreLink += ".html";
0272: } else if (doc instanceof ExecutableMemberDoc) {
0273: ExecutableMemberDoc execMemberDoc = (ExecutableMemberDoc) doc;
0274: String pkg = enclosing.containingPackage().name();
0275: if (!pkg.startsWith("java.")) {
0276: return;
0277: }
0278: String clazz = enclosing.name();
0279: String method = execMemberDoc.name();
0280: String sig = execMemberDoc.signature();
0281:
0282: jreLink += pkg.replace('.', '/') + "/";
0283: jreLink += clazz;
0284: jreLink += ".html";
0285: jreLink += "#";
0286: jreLink += method;
0287: jreLink += sig;
0288: } else if (doc instanceof PackageDoc) {
0289: String pkg = doc.name();
0290: if (!pkg.startsWith("java.")) {
0291: return;
0292: }
0293: jreLink += pkg.replace('.', '/') + "/package-summary.html";
0294: } else if (doc instanceof FieldDoc) {
0295: FieldDoc fieldDoc = (FieldDoc) doc;
0296: String pkg = enclosing.containingPackage().name();
0297: if (!pkg.startsWith("java.")) {
0298: return;
0299: }
0300: String clazz = fieldDoc.containingClass().name();
0301: String field = fieldDoc.name();
0302:
0303: jreLink += pkg.replace('.', '/') + "/";
0304: jreLink += clazz;
0305: jreLink += ".html";
0306: jreLink += "#";
0307: jreLink += field;
0308: }
0309:
0310: // Add the link.
0311: //
0312: beginln("jre");
0313: text(jreLink);
0314: endln();
0315: }
0316:
0317: private void emitLocation(Doc doc) {
0318: Doc parent = getParentDoc(doc);
0319: if (parent != null) {
0320: beginln("location");
0321: emitLocationLink(parent);
0322: endln();
0323: }
0324: }
0325:
0326: private void emitLocationLink(Doc doc) {
0327: // Intentionally reverses the order.
0328: //
0329: String myId;
0330: String myTitle;
0331: if (doc instanceof MemberDoc) {
0332: MemberDoc memberDoc = (MemberDoc) doc;
0333: myId = getId(memberDoc);
0334: myTitle = memberDoc.name();
0335: } else if (doc instanceof ClassDoc) {
0336: ClassDoc classDoc = (ClassDoc) doc;
0337: myId = getId(classDoc);
0338: myTitle = classDoc.name();
0339: } else if (doc instanceof PackageDoc) {
0340: PackageDoc pkgDoc = (PackageDoc) doc;
0341: myId = getId(pkgDoc);
0342: myTitle = pkgDoc.name();
0343: } else if (doc instanceof RootDoc) {
0344: myId = rootDocId;
0345: myTitle = initialRootDoc.name();
0346: } else {
0347: throw new IllegalStateException(
0348: "Expected only a member, type, or package");
0349: }
0350:
0351: Doc parent = getParentDoc(doc);
0352: if (parent != null) {
0353: emitLocationLink(parent);
0354: }
0355:
0356: beginln("link", "ref", myId);
0357:
0358: Tag[] titleTag = doc.tags("@title");
0359: if (titleTag.length > 0) {
0360: myTitle = titleTag[0].text().trim();
0361: }
0362:
0363: if (myTitle == null || myTitle.length() == 0) {
0364: myTitle = "[NO TITLE]";
0365: }
0366:
0367: text(myTitle);
0368:
0369: endln();
0370: }
0371:
0372: private void emitModifiers(ProgramElementDoc doc) {
0373: if (doc.isPrivate()) {
0374: beginEndln("isPrivate");
0375: } else if (doc.isProtected()) {
0376: beginEndln("isProtected");
0377: } else if (doc.isPublic()) {
0378: beginEndln("isPublic");
0379: } else if (doc.isPackagePrivate()) {
0380: beginEndln("isPackagePrivate");
0381: }
0382:
0383: if (doc.isStatic()) {
0384: beginEndln("isStatic");
0385: }
0386:
0387: if (doc.isFinal()) {
0388: beginEndln("isFinal");
0389: }
0390:
0391: if (doc instanceof MethodDoc) {
0392: MethodDoc methodDoc = (MethodDoc) doc;
0393:
0394: if (methodDoc.isAbstract()) {
0395: beginEndln("isAbstract");
0396: }
0397:
0398: if (methodDoc.isSynchronized()) {
0399: beginEndln("isSynchronized");
0400: }
0401: }
0402: }
0403:
0404: private void emitOutOfLineTags(Tag[] tags) {
0405: beginln("tags");
0406: processTags(tags);
0407: endln();
0408: }
0409:
0410: private void emitType(Type type) {
0411: ClassDoc typeAsClass = type.asClassDoc();
0412:
0413: if (typeAsClass != null) {
0414: begin("type", "ref", getId(typeAsClass));
0415: } else {
0416: begin("type");
0417: }
0418:
0419: String typeName = type.typeName();
0420: String dims = type.dimension();
0421:
0422: text(typeName + dims);
0423:
0424: end();
0425: }
0426:
0427: private void end() {
0428: pw.print("</" + tagStack.pop() + ">");
0429: }
0430:
0431: private void endCDATA() {
0432: pw.print("]]>");
0433: }
0434:
0435: private void endln() {
0436: end();
0437: pw.println();
0438: }
0439:
0440: private MethodDoc findMatchingInterfaceMethodDoc(
0441: ClassDoc[] interfaces, MethodDoc methodDoc) {
0442: if (interfaces != null) {
0443: // Look through the methods on superInterface for a matching methodDoc.
0444: //
0445: for (int intfIndex = 0; intfIndex < interfaces.length; ++intfIndex) {
0446: ClassDoc currentIntfDoc = interfaces[intfIndex];
0447: MethodDoc[] intfMethodDocs = currentIntfDoc.methods();
0448: for (int methodIndex = 0; methodIndex < intfMethodDocs.length; ++methodIndex) {
0449: MethodDoc intfMethodDoc = intfMethodDocs[methodIndex];
0450: String methodDocName = methodDoc.name();
0451: String intfMethodDocName = intfMethodDoc.name();
0452: if (methodDocName.equals(intfMethodDocName)) {
0453: if (methodDoc.signature().equals(
0454: intfMethodDoc.signature())) {
0455: // It's a match!
0456: //
0457: return intfMethodDoc;
0458: }
0459: }
0460: }
0461:
0462: // Try the superinterfaces of this interface.
0463: //
0464: MethodDoc foundMethodDoc = findMatchingInterfaceMethodDoc(
0465: currentIntfDoc.interfaces(), methodDoc);
0466: if (foundMethodDoc != null) {
0467: return foundMethodDoc;
0468: }
0469: }
0470: }
0471:
0472: // Just didn't find it anywhere. Must not be based on an implemented
0473: // interface.
0474: //
0475: return null;
0476: }
0477:
0478: private ExtraClassResolver getExtraClassResolver(Tag tag) {
0479:
0480: if (tag.holder() instanceof PackageDoc) {
0481: return new ExtraClassResolver() {
0482: public ClassDoc findClass(String className) {
0483: return initialRootDoc.classNamed(className);
0484: }
0485: };
0486: }
0487:
0488: return null;
0489: }
0490:
0491: private String getId(ClassDoc classDoc) {
0492: return classDoc.qualifiedName();
0493: }
0494:
0495: private String getId(ExecutableMemberDoc memberDoc) {
0496: // Use the mangled name to look up a unique id (based on its hashCode).
0497: //
0498: String clazz = memberDoc.containingClass().qualifiedName();
0499: String id = clazz + "#" + memberDoc.name()
0500: + memberDoc.signature();
0501: return id;
0502: }
0503:
0504: private String getId(FieldDoc fieldDoc) {
0505: String clazz = fieldDoc.containingClass().qualifiedName();
0506: String id = clazz + "#" + fieldDoc.name();
0507: return id;
0508: }
0509:
0510: private String getId(MemberDoc memberDoc) {
0511: if (memberDoc.isMethod()) {
0512: return getId((MethodDoc) memberDoc);
0513: } else if (memberDoc.isConstructor()) {
0514: return getId((ConstructorDoc) memberDoc);
0515: } else if (memberDoc.isField()) {
0516: return getId((FieldDoc) memberDoc);
0517: } else {
0518: throw new RuntimeException("Unknown member type");
0519: }
0520: }
0521:
0522: private String getId(PackageDoc packageDoc) {
0523: return packageDoc.name();
0524: }
0525:
0526: private Doc getParentDoc(Doc doc) {
0527: if (doc instanceof MemberDoc) {
0528: MemberDoc memberDoc = (MemberDoc) doc;
0529: return memberDoc.containingClass();
0530: } else if (doc instanceof ClassDoc) {
0531: ClassDoc classDoc = (ClassDoc) doc;
0532: Doc enclosingClass = classDoc.containingClass();
0533: if (enclosingClass != null) {
0534: return enclosingClass;
0535: } else {
0536: return classDoc.containingPackage();
0537: }
0538: } else if (doc instanceof PackageDoc) {
0539: return initialRootDoc;
0540: } else if (doc instanceof RootDoc) {
0541: return null;
0542: } else {
0543: throw new IllegalStateException(
0544: "Expected only a member, type, or package");
0545: }
0546: }
0547:
0548: private boolean looksSynthesized(ExecutableMemberDoc memberDoc) {
0549: SourcePosition memberPos = memberDoc.position();
0550: int memberLine = memberPos.line();
0551:
0552: SourcePosition classPos = memberDoc.containingClass()
0553: .position();
0554: int classLine = classPos.line();
0555:
0556: if (memberLine == classLine) {
0557: return true;
0558: } else {
0559: return false;
0560: }
0561: }
0562:
0563: private void process(ClassDoc enclosing, ClassDoc classDoc) {
0564: // Make sure it isn't a @skip-ped topic.
0565: //
0566: if (classDoc.tags("@skip").length > 0) {
0567: // This one is explicitly skipped right now.
0568: //
0569: return;
0570: }
0571:
0572: if (classDoc.isInterface()) {
0573: beginln("interface");
0574: } else {
0575: beginln("class");
0576: }
0577:
0578: emitIdentity(getId(classDoc), classDoc.name());
0579: emitLocation(classDoc);
0580: emitDescription(enclosing, classDoc, classDoc
0581: .firstSentenceTags(), classDoc.inlineTags());
0582: emitOutOfLineTags(classDoc.tags());
0583: emitModifiers(classDoc);
0584:
0585: ClassDoc super classDoc = classDoc.super class();
0586: if (super classDoc != null) {
0587: beginln("superclass", "ref", getId(super classDoc));
0588: text(super classDoc.name());
0589: endln();
0590: }
0591:
0592: ClassDoc[] super interfacesDoc = classDoc.interfaces();
0593: for (int i = 0; i < super interfacesDoc.length; i++) {
0594: ClassDoc super interfaceDoc = super interfacesDoc[i];
0595: beginln("superinterface", "ref", getId(super interfaceDoc));
0596: text(super interfaceDoc.name());
0597: endln();
0598: }
0599:
0600: ClassDoc[] cda = classDoc.innerClasses();
0601: for (int i = 0; i < cda.length; i++) {
0602: process(classDoc, cda[i]);
0603: }
0604:
0605: FieldDoc[] fda = classDoc.fields();
0606: for (int i = 0; i < fda.length; i++) {
0607: process(classDoc, fda[i]);
0608: }
0609:
0610: ConstructorDoc[] ctorDocs = classDoc.constructors();
0611: for (int i = 0; i < ctorDocs.length; i++) {
0612: process(classDoc, ctorDocs[i]);
0613: }
0614:
0615: MethodDoc[] methods = classDoc.methods();
0616: for (int i = 0; i < methods.length; i++) {
0617: process(classDoc, methods[i]);
0618: }
0619:
0620: endln();
0621: }
0622:
0623: private void process(ClassDoc enclosing,
0624: ExecutableMemberDoc memberDoc) {
0625: if (looksSynthesized(memberDoc)) {
0626: // Skip it.
0627: //
0628: return;
0629: }
0630:
0631: // Make sure it isn't a @skip-ped member.
0632: //
0633: if (memberDoc.tags("@skip").length > 0) {
0634: // This one is explicitly skipped right now.
0635: //
0636: return;
0637: }
0638:
0639: if (memberDoc instanceof MethodDoc) {
0640: beginln("method");
0641: emitIdentity(getId(memberDoc), memberDoc.name());
0642: emitLocation(memberDoc);
0643:
0644: // If this method is not explicitly documented, use the best inherited
0645: // one.
0646: //
0647: String rawComment = memberDoc.getRawCommentText();
0648: if (rawComment.length() == 0) {
0649: // Switch out the member doc being used.
0650: //
0651: MethodDoc methodDoc = (MethodDoc) memberDoc;
0652: MethodDoc super MethodDoc = methodDoc.overriddenMethod();
0653:
0654: if (super MethodDoc == null) {
0655:
0656: ClassDoc classDocToTry = methodDoc
0657: .containingClass();
0658: while (classDocToTry != null) {
0659: // See if this is a method from an interface.
0660: // If so, borrow its description.
0661: //
0662: super MethodDoc = findMatchingInterfaceMethodDoc(
0663: classDocToTry.interfaces(), methodDoc);
0664:
0665: if (super MethodDoc != null) {
0666: break;
0667: }
0668:
0669: classDocToTry = classDocToTry.super class();
0670: }
0671: }
0672:
0673: if (super MethodDoc != null) {
0674: // Borrow the description from the superclass/superinterface.
0675: //
0676: memberDoc = super MethodDoc;
0677: }
0678: }
0679: } else if (memberDoc instanceof ConstructorDoc) {
0680: beginln("constructor");
0681: emitIdentity(getId(memberDoc), memberDoc.containingClass()
0682: .name());
0683: emitLocation(memberDoc);
0684: } else {
0685: throw new IllegalStateException(
0686: "What kind of executable member is this?");
0687: }
0688:
0689: emitDescription(enclosing, memberDoc, memberDoc
0690: .firstSentenceTags(), memberDoc.inlineTags());
0691: emitOutOfLineTags(memberDoc.tags());
0692: emitModifiers(memberDoc);
0693:
0694: begin("flatSignature");
0695: text(memberDoc.flatSignature());
0696: end();
0697:
0698: // Return type if it's a method
0699: //
0700: if (memberDoc instanceof MethodDoc) {
0701: emitType(((MethodDoc) memberDoc).returnType());
0702: }
0703:
0704: // Parameters
0705: //
0706: beginln("params");
0707: Parameter[] pda = memberDoc.parameters();
0708: for (int i = 0; i < pda.length; i++) {
0709: Parameter pd = pda[i];
0710:
0711: begin("param");
0712: emitType(pd.type());
0713: begin("name");
0714: text(pd.name());
0715: end();
0716: end();
0717: }
0718: endln();
0719:
0720: // Exceptions thrown
0721: //
0722: ClassDoc[] tea = memberDoc.thrownExceptions();
0723: if (tea.length > 0) {
0724: beginln("throws");
0725: for (int i = 0; i < tea.length; ++i) {
0726: ClassDoc te = tea[i];
0727: beginln("throw", "ref", getId(te));
0728: text(te.name());
0729: endln();
0730: }
0731: endln();
0732: }
0733:
0734: // Maybe show code
0735: //
0736: if (showCode) {
0737: SourcePosition pos = memberDoc.position();
0738: if (pos != null) {
0739: beginln("code");
0740: String source = slurpSource(pos);
0741: begin("pre", "class", "code");
0742: beginCDATA();
0743: text(source);
0744: endCDATA();
0745: endln();
0746: endln();
0747: }
0748: }
0749:
0750: endln();
0751: }
0752:
0753: private void process(ClassDoc enclosing, FieldDoc fieldDoc) {
0754: // Make sure it isn't @skip-ped.
0755: //
0756: if (fieldDoc.tags("@skip").length > 0) {
0757: // This one is explicitly skipped right now.
0758: //
0759: return;
0760: }
0761:
0762: String commentText = fieldDoc.commentText();
0763: if (fieldDoc.isPrivate()
0764: && (commentText == null || commentText.length() == 0)) {
0765: return;
0766: }
0767:
0768: beginln("field");
0769: emitIdentity(fieldDoc.qualifiedName(), fieldDoc.name());
0770: emitLocation(fieldDoc);
0771: emitDescription(enclosing, fieldDoc, fieldDoc
0772: .firstSentenceTags(), fieldDoc.inlineTags());
0773: emitOutOfLineTags(fieldDoc.tags());
0774: emitModifiers(fieldDoc);
0775: emitType(fieldDoc.type());
0776: endln();
0777: }
0778:
0779: private void process(PackageDoc packageDoc) {
0780: beginln("package");
0781:
0782: emitIdentity(packageDoc.name(), packageDoc.name());
0783: emitLocation(packageDoc);
0784: emitDescription(null, packageDoc, packageDoc
0785: .firstSentenceTags(), packageDoc.inlineTags());
0786: emitOutOfLineTags(packageDoc.tags());
0787:
0788: // Top-level classes
0789: //
0790: ClassDoc[] cda = packageDoc.allClasses();
0791: for (int i = 0; i < cda.length; i++) {
0792: ClassDoc cd = cda[i];
0793:
0794: // Make sure we have source.
0795: //
0796: SourcePosition p = cd.position();
0797: if (p == null || p.line() == 0) {
0798: // Skip this since it isn't ours (otherwise we would have source).
0799: //
0800: continue;
0801: }
0802:
0803: if (cd.containingClass() == null) {
0804: process(cd, cda[i]);
0805: } else {
0806: // Not a top-level class.
0807: //
0808: cd = cda[i];
0809: }
0810: }
0811:
0812: endln();
0813: }
0814:
0815: private void process(RootDoc rootDoc) {
0816: try {
0817: initialRootDoc = rootDoc;
0818: File outputFile = new File(outputPath);
0819: outputFile.getParentFile().mkdirs();
0820: FileWriter fw = new FileWriter(outputFile);
0821: pw = new PrintWriter(fw, true);
0822:
0823: beginln("booklet");
0824:
0825: rootDocId = "";
0826: String title = "";
0827: Tag[] idTags = rootDoc.tags("@id");
0828: if (idTags.length > 0) {
0829: rootDocId = idTags[0].text();
0830: } else {
0831: initialRootDoc
0832: .printWarning("Expecting @id in an overview html doc; see -overview");
0833: }
0834:
0835: Tag[] titleTags = rootDoc.tags("@title");
0836: if (titleTags.length > 0) {
0837: title = titleTags[0].text();
0838: } else {
0839: initialRootDoc
0840: .printWarning("Expecting @title in an overview html doc; see -overview");
0841: }
0842:
0843: emitIdentity(rootDocId, title);
0844: emitLocation(rootDoc);
0845: emitDescription(null, rootDoc, rootDoc.firstSentenceTags(),
0846: rootDoc.inlineTags());
0847: emitOutOfLineTags(rootDoc.tags());
0848:
0849: // Create a list of the packages to iterate over.
0850: //
0851: HashSet packageNames = new HashSet();
0852: ClassDoc[] cda = initialRootDoc.classes();
0853: for (int i = 0; i < cda.length; i++) {
0854: ClassDoc cd = cda[i];
0855: // Only top-level classes matter.
0856: //
0857: if (cd.containingClass() == null) {
0858: packageNames.add(cd.containingPackage().name());
0859: }
0860: }
0861:
0862: // Packages
0863: //
0864: for (Iterator iter = packageNames.iterator(); iter
0865: .hasNext();) {
0866: String packageName = (String) iter.next();
0867:
0868: // Only process this package if either no "docpkg" is set, or it is
0869: // included.
0870: //
0871: if (packagesToGenerate == null
0872: || packagesToGenerate.contains(packageName)) {
0873: PackageDoc pd = initialRootDoc
0874: .packageNamed(packageName);
0875: process(pd);
0876: }
0877: }
0878:
0879: endln();
0880: } catch (Exception e) {
0881: e.printStackTrace();
0882: initialRootDoc.printError("Caught exception: "
0883: + e.toString());
0884: }
0885: }
0886:
0887: private void processSeeTag(SeeTag seeTag) {
0888: String ref = null;
0889: ClassDoc cd = null;
0890: PackageDoc pd = null;
0891: MemberDoc md = null;
0892: String title = null;
0893:
0894: // Check for HTML links
0895: if (seeTag.text().startsWith("<")) {
0896: // TODO: ignore for now
0897: return;
0898: }
0899: // Ordered: most-specific to least-specific
0900: if (null != (md = seeTag.referencedMember())) {
0901: ref = getId(md);
0902: } else if (null != (cd = seeTag.referencedClass())) {
0903: ref = getId(cd);
0904:
0905: // See if the target has a title.
0906: //
0907: Tag[] titleTag = cd.tags("@title");
0908: if (titleTag.length > 0) {
0909: title = titleTag[0].text().trim();
0910: if (title.length() == 0) {
0911: title = null;
0912: }
0913: }
0914: } else if (null != (pd = seeTag.referencedPackage())) {
0915: ref = getId(pd);
0916: }
0917:
0918: String label = seeTag.label();
0919:
0920: // If there is a label, use it.
0921: if (label == null || label.trim().length() == 0) {
0922:
0923: // If there isn't a label, see if the @see target has a @title.
0924: //
0925: if (title != null) {
0926: label = title;
0927: } else {
0928: label = seeTag.text();
0929:
0930: if (label.endsWith(".")) {
0931: label = label.substring(0, label.length() - 1);
0932: }
0933:
0934: // Rip off all but the last interesting part to prevent fully-qualified
0935: // names everywhere.
0936: //
0937: int last1 = label.lastIndexOf('.');
0938: int last2 = label.lastIndexOf('#');
0939:
0940: if (last2 > last1) {
0941: // Use the class name plus the member name.
0942: //
0943: label = label.substring(last1 + 1)
0944: .replace('#', '.');
0945: } else if (last1 != -1) {
0946: label = label.substring(last1 + 1);
0947: }
0948:
0949: if (label.charAt(0) == '.') {
0950: // Started with "#" so remove the dot.
0951: //
0952: label = label.substring(1);
0953: }
0954: }
0955: }
0956:
0957: if (ref != null) {
0958: begin("link", "ref", ref);
0959: text(label != null ? label.trim() : "");
0960: end();
0961: } else {
0962: initialRootDoc.printWarning(seeTag.position(),
0963: "Unverifiable cross-reference to '" + seeTag.text()
0964: + "'");
0965: // The link probably won't work, but emit it anyway.
0966: begin("link");
0967: text(label != null ? label.trim() : "");
0968: end();
0969: }
0970: }
0971:
0972: private void processTags(Tag[] tags) {
0973: for (int i = 0; i < tags.length; i++) {
0974: Tag tag = tags[i];
0975: String tagKind = tag.kind();
0976: if (tagKind.equals("Text")) {
0977: text(tag.text());
0978: } else if (tagKind.equals("@see")) {
0979: processSeeTag((SeeTag) tag);
0980: } else if (tagKind.equals("@param")) {
0981: ParamTag paramTag = (ParamTag) tag;
0982: beginln("param");
0983: begin("name");
0984: text(paramTag.parameterName());
0985: end();
0986: begin("description");
0987: processTags(paramTag.inlineTags());
0988: end();
0989: endln();
0990: } else if (tagKind.equals("@example")) {
0991: ExtraClassResolver extraClassResolver = getExtraClassResolver(tag);
0992: SourcePosition pos = LinkResolver.resolveLink(tag,
0993: extraClassResolver);
0994: String source = slurpSource(pos);
0995: begin("pre", "class", "code");
0996: beginCDATA();
0997: text(source);
0998: endCDATA();
0999: endln();
1000: } else if (tagKind.equals("@gwt.include")) {
1001: String contents = ResourceIncluder
1002: .getResourceFromClasspathScrubbedForHTML(tag);
1003: begin("pre", "class", "code");
1004: text(contents);
1005: endln();
1006: } else if (!standardTagKinds.contains(tag.name())) {
1007: // Custom tag; pass it along other tag.
1008: //
1009: String tagName = tag.name().substring(1);
1010: begin(tagName);
1011: processTags(tag.inlineTags());
1012: end();
1013: }
1014: }
1015: }
1016:
1017: private void text(String s) {
1018: pw.print(s);
1019: }
1020: }
|