Let JavaDoc produce tables

JavaDoc is good for documenting Java code. But sometimes it is better to have tables than plain text. Of course you could code the HTML code for tables manually, but it is more comfortable if JavaDoc would support that ‚requirement‘ directly – which it does not. But you can extend JavaDoc by writing a Taglet.

We want to achieve that your JavaDoc comment

/**
@my.tag
    #column1 Text for column 1
    #column2 Text for column 2
*/

produces the required HTML.

JavaDoc uses Doclets as extension point. Writing a doclet is possible but too much overhead because its responsibility is to do the whole generation, but we only want to add a new feature. By default the StandardDoclet is used. And that provides an additional extension point: the taglet. With that you can write new JavaDoc tags (like the @my.tag) and specify the generation behaviour. Using Taglets consist on these steps:

  • writing an implementation of com.sun.tools.doclets.Taglet
  • add the taglet to the generation process, e.g. by Ant’s <javadoc><taglet name=classname path=pathOfJar>.

When you implement the Taglet interface you have to implement these methods


public String getName() {
  return NAME;
}
public boolean inField() {
  return false;
}
public boolean inConstructor() {
  return false;
}
public boolean inMethod() {
  return true;
}
public boolean inOverview() {
  return false;
}
public boolean inPackage() {
  return false;
}
public boolean inType() {
  return false;
}
public boolean isInlineTag() {
  return false;
}

public static void register(Map registeredTaglets);
public String toString(Tag tag);
public String toString(Tag[] tags);

getName() returns the name of the taglet which should be the name used after the @ sign. I used a constant NAME=“my.tag“.

The inXXX and isXXX methods specify the kind of the taglet. Because I want to have the tables only in method documentation this taglet is not valid for fields and constructors, types (means the class itself) or package, but for methods. Its content shouldnt be on the overview page (only on the detail page). This taglet shouldnt define an inline tag (like in ‚text text {@link target} text text‘).

The next three methods are the more interesting …

The register() method is a static method (and therefore not defined by the interface) and responsible for registering the taglet by the StandardDoclet. If you specify the -taglet option or use the <taglet> instruction in Ant that method is invoked. The StandardDoclet passes a Map with all registered taglets. So do these steps

  1. remove the (old) registration if present
  2. add the new registration

public static void register(Map registeredTaglets) {
  MyTaglet tag = new MyTaglet();
  Taglet t = (Taglet) registeredTaglets.get(tag.getName());
  if (t != null) {
    registeredTaglets.remove(tag.getName());
  }
  registeredTaglets.put(tag.getName(), tag);
}

The two toString() methods are responsible for the conversion from the taglet text to the HTML code. The first gets one taglet comment, the second gets multiple comments. So the easiest implementation for the ‚one‘ method is delegating to the ‚multiple‘ method.


public String toString(Tag tag) {
  return toString(new Tag[]{tag});
}

So the last thing to do is implementing the conversion of the tag-array:

  1. print the tag name as header
  2. indent the output as all other (standard) tags do
  3. print the table header
  4. for each tag print the row
  5. close opened HTML tags

public String toString(Tag[] tags) {
  // Quick exit
  if (tags == null || tags.length == 0) {
    return null;
  }
  // HTML collector
  StringBuffer sb = new StringBuffer();
  // 1.Header
  sb.append("<dt><b>").append(getName()).append("</b></dt>").append(BR);
  // 2.Indent
  sb.append("<dd>").append(BR);
  // 3.table header
  sb.append("
<table border='1'>").append(BR);
  sb.append("
<tr>").append(BR);
  sb.append("
<th>column 1</th>
").append(BR);
  sb.append("
<th>column 2</th>
").append(BR);
  sb.append("</tr>
").append(BR);
  // 4. for each tag make a row
  String[] stopwords = new String[]{"column1", "column2"};
  for (int i = 0; i < tags.length; i++) {
    appendTagAsHtml(sb, new TagInfo(tags&#91;i&#93;.text(), stopwords));
  }
  // 5.close all opened tags
  sb.append("</table>
").append(BR);
  sb.append("</dd>").append(BR);
  // return the HTML coding
  return sb.toString();
}

In this code I use two helpers: the method appendTagAsHtml() and the class TagInfo.

The TagInfo class constructor takes the text for the tag (all after the @tagname until the next @tag or end of javadoc) and the supported stopwords. It creates a slot for each stopword and one for the text before the first stopword. Then it parses the text and stores the words in the current slot. It starts with the before-slot and goes to the next slot if a stopword occurs.


public class TagInfo {
  HashMap<String,List<String>> map = new HashMap();

  TagInfo(String tagText, String[] stopwords) {
    map.put("#__text__", new ArrayList());
    for (int i = 0; i < stopwords.length; i++) {
      map.put("#" + stopwords&#91;i&#93;, new ArrayList());
    }
    String currentSlot = "#__text__";
    StringTokenizer tokenizer = new StringTokenizer(tagText);
    while (tokenizer.hasMoreTokens()) {
      String token = tokenizer.nextToken();
      if (map.containsKey(token)) {
        currentSlot = token;
      } else {
        ((List)map.get(currentSlot)).add(token);
      }
    }
  }

  public List get(String stopword) {
    return (List)map.get("#" + stopword);
  }
}

&#91;/sourcecode&#93;

The appendInfoAlsHtml() method converts the TagInfo to HTML. It uses another helper method for converting lists to strings.

&#91;sourcecode language='java'&#93;

private static void appendInfoAlsHtml2(StringBuffer sb, PruefungsInfo info) {
  sb.append("
<tr valign='top'>").append(BR);
  sb.append("
<td>")
    .append(list2stringbuffer(info.get("column1"), " "))
    .append("</td>
")
    .append(BR);
  sb.append("
<td>")
    .append(list2stringbuffer(info.get("column2"), " "))
    .append("</td>
")
    .append(BR);
    .append("</td>
")
    .append(BR);
    .append(BR);
  sb.append("</tr>
").append(BR);
}

protected static StringBuffer list2stringbuffer(List list, String separator) {
  StringBuffer rv = new StringBuffer();
  for (int i=0; i
	<list.size()-1; i++) {
    rv.append(list.get(i));
    rv.append(separator);
  }
  if (list.size() > 0) {
    // 'get' is 0-based but 'size' is 1-based
    rv.append(list.get(list.size()-1));
  }
  return rv;
}

Finally the use of the taglet in Ant is this:


<target name="javadoc">
  <mkdir dir="${javadoc.dir}"/>
  <javadoc sourcepath="${java.src}"
           destdir="${javadoc.dir}"
           author="${javadoc.author}"
           version="${javadoc.version}"
           use="${javadoc.use}"
           access="${javadoc.access}"
           excludepackagenames="${javadoc.excludes}"
           windowtitle="${ant.project.name} - API">
    <doctitle><!&#91;CDATA&#91;
<h1>${ant.project.name} - API</h1>
&#93;&#93;></doctitle>
    <bottom><!&#91;CDATA&#91;<i>Copyright &#169; 2002-${tstamp.jahr} ACME All Rights Reserved.</i>&#93;&#93;></bottom>
    <taglet name="pack.age.of.MyTaglet" path="${taglet.jar}"/>
  </javadoc>
</target>

p.s.

Sorry for unintentional linebreaks in the coding. Maybe my WordPress knowledge is not big enough …

Explore posts in the same categories: Ant, en

Schlagwörter: , ,

You can comment below, or link to this permanent URL from your own site.

5 Kommentare - “Let JavaDoc produce tables”

  1. jason kenny Says:

    Nice writing. You are on my RSS reader now so I can read more from you down the road.

  2. janmaterne Says:

    Thank you. I hope I can write more interesting material😉
    Jan


  3. I wouldn’t mind writing some HTML. What I find most annoying about javadoc table support is that the default CSS renders the cells with no borders at all. Ugh😦

    • Jan Says:

      Sure, you can write HTML. If you write HTML you also could specify the style-Attribute. Or provide an (external/internal) CSS and specify the class-Attribute of the table.

      Have you tried that?

      When writing the taglet, I wanted to make writing tables easier…


      • Regular javadoc comments with an HTML tag here and there is just about right concerning legibility. But if I have to stick inline CSS as well – the comments will become a pain to read and edit. No, thank you🙂

        I find the external stylesheet option more agreable.

        But more often I just stick to the simple method of tabulating by hand using the good old pre🙂

        By the way, this article of yours is just about the only sensible thing I found on the web regarding javadoc tables🙂


Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s


%d Bloggern gefällt das: