Building a JSDoc Runner for Maven

The next step in this plugin is to ensure that we can generate documentation. While it may seem like a stretch to do this so early in the process, it forces us to build a few other pieces that we’ll need later.

Our documentation generator will be JSDoc, chosen because it’s fairly mature, has a strong following, and its markup is broadly supported across IDE’s. In fact JSDoc seems to be one of the few broadly adopted standards out there, though to speculate why is beyond the scope of this post. What matters is that it’s a documentation writer written in Javascript that is run via Mozilla’s Rhino runtime. To properly run it we’ll have to resolve the dependency, unpack it into a temporary directory, and then spin up Rhino to execute the code.

I encountered a problem in the way JSDoc 2.4.0 interacts with Rhino 1.7RC3, a bug introduced by two inline function declarations without semicolons in TokenParser.js. While on one side one might argue that this is a bug in Rhino, declaring functions inside of other functions is a big no-no in the javascript world. As a result I’ve had to generate and deploy my own patched version of JSDoc.

Resolving JSDoc

All of the plugin artifacts for a given mojo are easily available as a parameter, thus locating the proper version of JSDoc is fairly simple.

Full source available on github

 * @parameter default-value="${plugin.artifacts}"
private List<Artifact> pluginArtifacts;

public Artifact locate(String groupId, String artifactId) {

	getLog().info("Resolving " + groupId + ":" + artifactId);

	for (Iterator<Artifact> iterator = pluginArtifacts.iterator(); iterator.hasNext();) {
		Artifact artifact = (Artifact);

		if (artifact.getGroupId().equals(groupId) && artifact.getArtifactId().equals(artifactId)) {
			return artifact;

	getLog().error(String.format("Failed to locate %s:%s", groupId, artifactId));
	getLog().error("This probably means that you didn't specify it as a dependency in the pom.xml file");

	throw new RuntimeException(String.format("Failed to locate %s:%s", groupId, artifactId));

Unpacking JSDoc

As all jar files are zip files, we can simply write a zip extraction routine to unpack it.

Full source available on github

ZipFile zipFile = new ZipFile(artifact.getFile(), ZipFile.OPEN_READ);
Enumeration zipFileEntries = zipFile.entries();

while (zipFileEntries.hasMoreElements()) {
	ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();

	// Construct the new name
	String currentName = entry.getName();
	File destFile = new File(destinationDirectory, currentName);

	if (entry.isDirectory()) {
		// If we are a directory, create it.
	} else {
		// If we're a file, unzip it.
		BufferedInputStream is = new BufferedInputStream(zipFile.getInputStream(entry));
		int currentByte;

		// establish buffer for writing file
		byte data[] = new byte[BUFFER];

		// write the current file to disk
		FileOutputStream fos = new FileOutputStream(destFile);
		BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);

		// read and write until last byte is encountered
		while ((currentByte =, 0, BUFFER)) != -1) {
			dest.write(data, 0, currentByte);

Configure JSDoc

At this point we’re back in the drudgery of maven configuration. Since we’d like to keep our configurations relatively separate (in a <jsdoc> sub tag in the mojo configuration), I’ve created a separate JSDocOptions class that can accept all of our options. I’m not going to bother copying it all here as there’s quite a bit of code, but you can see the full source code here.

Running JSDoc via Rhino

Lastly, we have to execute JSDoc, which is done via Rhino. This, again, is fairly straightforward.

Full source available on github

public void run(File inputDirectory, File outputDirectory, JSDocOptions options)
	throws JSDocRunnerException
	// Build the string arguments.
	String[] args = options.buildArguments(jsDocRoot, inputDirectory, outputDirectory);

	// Run the javascript engine.
	getLog().info("Building JSDoc...");

	// Create our logging streams
	MojoLoggerOutputStream errorStream = new MojoLoggerOutputStream(LogLevel.ERR);
	Main.setErr(new PrintStream(errorStream));

	MojoLoggerOutputStream outputStream = new MojoLoggerOutputStream(LogLevel.WARN);
	Main.setOut(new PrintStream(outputStream));




  • As a side note: I'm starting to realize that the correct approach to the dependency resolution is not, actually, to resolve an independent resource, but rather to package a known version of JSDoc in my Mojo directly. That update will come once I start cleaning and refactoring in preparation to the 1.0 release.

Leave a Reply