// Copyright (c) 2006 - 2008, Markus Strauch.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package net.sf.sdedit.taglet;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.sf.sdedit.config.Configuration;
import net.sf.sdedit.config.ConfigurationManager;
import net.sf.sdedit.diagram.Diagram;
import net.sf.sdedit.text.TextHandler;
import net.sf.sdedit.ui.ImagePaintDevice;
import com.sun.javadoc.Doc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.Tag;
import com.sun.tools.doclets.internal.toolkit.taglets.Taglet;
import com.sun.tools.doclets.internal.toolkit.taglets.TagletOutput;
import com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter;
/**
* This is a taglet that generates sequence diagrams from the contents of
* <tt>@sequence.diagram</tt> tags. The diagrams are saved as PNG files in a child
* directory of the javadoc destination directory (as
* specified by the -d option). Inside the html pages
* generated by javadoc, the files are referenced using a
* relative path, so the references remain valid if the pages
* are moved - for example, uploaded to a webserver.
* <p>
* If the first line of the tag content is "quoted" it will be
* used as title for the diagram instead of the default
* "Sequence Diagram:"
* <p>
* <tt>@sequence.diagram</tt> are no inline tags and they can be used inside
* classes, and inside package documentation.
*
* @sequence.diagram "Processing of <tt>@sequence.diagram</tt> tags" user:Actor javadoc:Javadoc[a] sourceFile:File
* /docFile:File taglet:SequenceTaglet
*
* user:javadoc.generateDocumentation() [c:loop for all source files]
* javadoc:sourceFile.read() javadoc:docFile.new() [c:loop "sequence.diagram"
* tag found] javadoc:output=taglet.getTagletOutput(tag)
* javadoc:docFile.append(output) [/c] [/c]
*
* @author Markus Strauch
* @author �ystein Lunde
*/
public class SequenceTaglet implements Taglet
{
private static final String tagName = "sequence.diagram";
/**
* The name of the child directory of the javadoc destination directory
* where the png files are stored.
*/
private static final String imageSubDirectory = "sequence-diagrams";
/**
* The directory where the png files are stored.
*/
private File diagramDirectory;
/**
* Counts how often an image base name has been used
*/
private Map<String, Integer> nameCounter;
/**
* Registers an instance of this taglet class.
*
* @param tagletMap
* used for registering (maps tag names onto taglets)
*/
public static void register(Map<String, Taglet> tagletMap) {
SequenceTaglet tag = new SequenceTaglet();
Taglet t = (Taglet) tagletMap.get(tag.getName());
if (t != null) {
tagletMap.remove(tag.getName());
}
tagletMap.put(tag.getName(), tag);
}
private SequenceTaglet() {
nameCounter = new HashMap<String, Integer>();
}
private String getPathToJavadocDestination(Tag tag)
throws SequenceTagletException {
PackageDoc pdoc = null;
if (tag.holder() instanceof PackageDoc) {
pdoc = (PackageDoc) tag.holder();
} else {
ProgramElementDoc pedoc = (ProgramElementDoc) tag.holder();
pdoc = pedoc.containingPackage();
}
if (pdoc.allClasses() == null || pdoc.allClasses().length == 0) {
throw new SequenceTagletException(
"Cowardly refusing to generate a sequence diagram for an empty package",
null);
}
String qualifiedName = pdoc.allClasses()[0].qualifiedName();
String path = "";
for (int i = 0; i < qualifiedName.length(); i++) {
if (qualifiedName.charAt(i) == '.') {
path += "../";
}
}
return path;
}
/**
* Returns the base name of the image to be generated, which is the name of
* the holder of the tag containing the diagram specification, followed by a
* unique number.
*
* @param tag
* a sequence.diagram tag
* @return the name of its holder, followed by a unique number
*/
private String getImageName(Tag tag) {
String name = tag.holder().name();
Integer count = nameCounter.get(name);
if (count == null) {
count = 1;
} else {
count++;
}
nameCounter.put(name, count);
return name + "-" + count;
}
/**
* Creates a sequence diagram image from a part of the contents of a
* sequence.diagram tag, saves it in the image directory and returns HTML
* code that references the image.
*
* @param path
* the path to the directory where the diagram image is to be
* stored (usually a path going upwards, i. e. containing ../'s)
* @param imageBaseName
* the base name of the image to be stored
* @param source
* string array containing the lines of the diagram specification
* @return the output that is to appear on the javadoc page (i. e. the
* <img> tag referencing the image
* @throws SequenceTagletException
* if the creation of the diagram fails
*/
private String generateOutput(String path, String imageBaseName,
String[] source) throws SequenceTagletException {
if (source == null || source.length == 0) {
return "";
}
// Set custom diagram title if first line is quoted
String diagramTitle = null;
if (source[0].trim().matches("^[\"'].*[\"']$")) {
// Set the title and remove the title quote
diagramTitle = source[0].replaceAll("[\"']", "");
// Remove the custom title from the sd spec
source[0] = "";
}
StringBuffer buffer = new StringBuffer();
for (String string : source) {
string = string.trim();
if (string.startsWith("<") && string.endsWith(">")) {
continue;
}
buffer.append(string + "\n");
}
String specification = buffer.toString().trim();
if (specification.length() == 0) {
return "";
}
Configuration conf = ConfigurationManager.createNewDefaultConfiguration().getDataObject();
conf.setHeadWidth(25);
conf.setMainLifelineWidth(5);
conf.setSubLifelineWidth(5);
conf.setThreaded(true);
conf.setGlue(3);
ImagePaintDevice device = new ImagePaintDevice();
TextHandler handler = new TextHandler(specification);
try {
new Diagram(conf, handler, device).generate();
} catch (Exception e) {
int error = handler.getLineNumber();
StringBuffer code = new StringBuffer("<br><tt>");
for (int i = 0; i < source.length; i++) {
String html = source[i].replaceAll("&", "&").replaceAll(
"<", "<").replaceAll(">", ">").replaceAll("\"",
""");
if (i == error) {
html = "<FONT COLOR=\"red\"><U><B>" + html
+ "</B></U></FONT>";
}
code.append(html + "<br>");
}
throw new SequenceTagletException(
"Malformed diagram specification: " + e.getMessage(),
"<DT><HR><B>Sequence Diagram:</B></DT>"
+ "<DD><B>Could not create sequence diagram: "
+ "<font color=\"red\">" + e.getMessage()
+ "</font></B>" + code.toString() + "</DD>");
}
String fileName = imageBaseName + ".png";
File imageFile = new File(diagramDirectory, fileName);
try {
device.saveImage(imageFile.getAbsolutePath());
} catch (IOException ioe) {
throw new SequenceTagletException("Could not save diagram image: "
+ ioe.getMessage(), "");
}
if (diagramTitle == null) {
diagramTitle = "Sequence Diagram " + imageBaseName;
}
String imageUrl = path + imageSubDirectory + "/" + fileName;
String anchor = "<A name=\"" + imageBaseName + "\"/>";
return "<DT><HR><B>" + anchor + diagramTitle + ":</B><P></DT>"
+ "<DD><img src='" + imageUrl + "'></DD>";
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#getName()
*/
public String getName() {
return tagName;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inConstructor()
*/
public boolean inConstructor() {
return true;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inField()
*/
public boolean inField() {
return true;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inMethod()
*/
public boolean inMethod() {
return true;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inOverview()
*/
public boolean inOverview() {
return false;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inPackage()
*/
public boolean inPackage() {
return true;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#inType()
*/
public boolean inType() {
return true;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#isInlineTag()
*/
public boolean isInlineTag() {
return false;
}
private String toString(Tag tag, TagletWriter writer) {
String output;
try {
String path = getPathToJavadocDestination(tag);
output = generateOutput(path, getImageName(tag), tag.text().split(
"\n"));
} catch (SequenceTagletException ste) {
writer.configuration().message.warning("doclet.in", ste
.getMessage(), tag.holder().name());
return ste.output;
}
return output;
}
private String toString(Tag[] tags, TagletWriter writer) {
if (tags != null && tags.length > 0) {
String output = "";
for (Tag tag : tags) {
try {
output += generateOutput(getPathToJavadocDestination(tag),
getImageName(tag), tag.text().split("\n"));
} catch (SequenceTagletException ste) {
writer.configuration().message.warning("doclet.in", ste
.getMessage(), tags[0].holder().name());
output += ste.output;
}
}
return output;
} else {
return null;
}
}
private void setDestinationDirectory(String dir) {
File baseDir = new File(dir);
diagramDirectory = new File(baseDir, imageSubDirectory);
diagramDirectory.mkdirs();
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#getTagletOutput(com.sun.javadoc.Tag,
* com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter)
*/
public TagletOutput getTagletOutput(Tag tag, TagletWriter writer)
throws IllegalArgumentException {
if (diagramDirectory == null) {
setDestinationDirectory(writer.configuration().destDirName);
}
TagletOutput out = writer.getTagletOutputInstance();
out.setOutput(toString(tag, writer));
return out;
}
/**
* @see com.sun.tools.doclets.internal.toolkit.taglets.Taglet#getTagletOutput(com.sun.javadoc.Doc,
* com.sun.tools.doclets.internal.toolkit.taglets.TagletWriter)
*/
public TagletOutput getTagletOutput(Doc holder, TagletWriter writer)
throws IllegalArgumentException {
if (diagramDirectory == null) {
setDestinationDirectory(writer.configuration().destDirName);
}
TagletOutput out = writer.getTagletOutputInstance();
Tag[] tags = holder.tags(getName());
if (tags.length == 0) {
return null;
}
out.setOutput(toString(tags, writer));
return out;
}
private static class SequenceTagletException extends Exception
{
/**
* Appears on the HTML page
*/
String output;
/**
*
* @param warning
* the warning that is sent to the doclet
* @param output
* the output that appears on the HTML page
*/
SequenceTagletException(String warning, String output) {
super(warning);
this.output = output;
}
}
}