Posted tagged ‘javadoc’

Ant: Use optional dependencies in JavaDoc

19. November 2009

If you create your Javadocs you sometimes get unresolved reference warnings: package xy does not exist or cannot find symbol

If you define the path with these dependencies outside of the target you could reference that path using the classpathref attribute.

But if the path definition is done in an other target and you don’t want to have a dependency on that (e.g. many dependencies are downloaded there, e.g. using Ivy), using the classpathref would throw a BuildException because the path is not defined: Reference my-path.reference not found.

You could do a little hack: use a classpath attribute and use a special PropertyEvaluator for using that path: classpathref=“${toString:my-path.reference}“. If your defining target is executed before, the toString: resolves that into a usable path-string. If the target is not executed before, the resulting string toString:my-path.reference is just ignored by the javadoc task.

Let JavaDoc produce tables

12. Januar 2009

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 …