package com.postspectacular.doclet;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doclet;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.ParamTag;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.SeeTag;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;
/**
* This custom doclet generates Javadocs in Textile markup. Textile is supported
* by popular wiki engines (e.g. Confluence) and as well is suitable for later
* translation into a number of other formats (e.g. Docbook, XHTML etc.). All
* generated files are stored as flat hierarchy in the specified target
* directory and are named as their corresponding qualified java types.
*
* @author Karsten Schmidt
*/
public class TextileDoclet extends Doclet {
private static final String JAVA_API_URL = "http://download.oracle.com/javase/6/docs/api/";
private static String getOption(RootDoc root, String option) {
for (String[] o : root.options()) {
if (o[0].equals(option)) {
return o[1];
}
}
return null;
}
public static int optionLength(String option) {
if (option.equals("-d")) {
return 2;
}
if (option.equals("-module")) {
return 2;
}
return 0;
}
public static boolean start(RootDoc root) {
String targetDir = getOption(root, "-d");
String moduleName = getOption(root, "-module");
TextileDoclet doclet = new TextileDoclet(root);
doclet.setTargetDir(targetDir);
doclet.setModuleName(moduleName);
doclet.buildPackagePage();
doclet.buildClassPages();
doclet.buildMetadata();
return true;
}
private final RootDoc root;
private String targetDir;
private String moduleName;
public TextileDoclet(RootDoc root) {
this.root = root;
}
private String buildClassMetaData(ClassDoc cd) {
StringBuffer buf = new StringBuffer();
buf.append("| *type* | ");
if (cd.isOrdinaryClass()) {
buf.append("class |\n");
buf.append("| *abstract* | ");
buf.append(cd.isAbstract() ? "yes" : "no");
buf.append(" |\n");
} else if (cd.isInterface()) {
buf.append("interface |\n");
} else if (cd.isAnnotationType()) {
buf.append("annotation |\n");
} else if (cd.isEnum()) {
buf.append("enum |\n");
}
System.out.println(cd.enumConstants().length);
buf.append("\n");
return buf.toString();
}
private void buildClassPages() {
for (ClassDoc cd : root.classes()) {
StringBuffer buf = new StringBuffer();
buf.append("h1. ").append(cd.qualifiedName()).append("\n\n");
buf.append(buildClassMetaData(cd));
buf.append(parseComment(cd.inlineTags())).append("\n\n");
if (!cd.isInterface() && !cd.isEnum()) {
buf.append("h2. Constructors\n\n");
buf.append(parseMembers(cd.constructors())).append("\n\n");
}
buf.append("h2. Methods\n\n");
buf.append(parseMembers(cd.methods())).append("\n\n");
writeFile(cd.qualifiedName() + ".textile", buf.toString());
}
}
private void buildMetadata() {
StringBuffer buf = new StringBuffer();
SimpleDateFormat fmt = new SimpleDateFormat(
"yyyy-MM-dd (HH:mm:ss 'GMT')");
fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
buf.append("Generated by [TextileDoclet|http://hg.postspectacular.com/textile-doclet] on "
+ fmt.format(new Date()));
writeFile("DocGenerator.textile", buf.toString());
}
private void buildPackageClassesPage(PackageDoc pdoc) {
StringBuffer buf = new StringBuffer();
buf.append("h1. ").append(pdoc.name()).append("\n\n");
String comment = parseComment(pdoc.inlineTags());
if (comment.length() > 0) {
buf.append(comment).append("\n\n");
}
buf.append("h2. Package overview\n\n");
List<ClassDoc> classes = Arrays.asList(pdoc.allClasses(true));
Collections.sort(classes, new Comparator<ClassDoc>() {
public int compare(ClassDoc a, ClassDoc b) {
return a.name().compareTo(b.name());
}
});
for (ClassDoc cd : classes) {
buf.append("* [").append(cd.name()).append('|')
.append(cd.qualifiedName()).append("]\n");
}
writeFile(pdoc.name() + ".textile", buf.toString());
}
private void buildPackagePage() {
StringBuffer buf = new StringBuffer();
buf.append("h1. ").append(moduleName).append("\n\n");
// TODO add module description from ???
buf.append("h2. List of packages in this module\n\n");
for (PackageDoc p : root.specifiedPackages()) {
if (p.allClasses().length > 0) {
buf.append("h3. [").append(p.name()).append("]\n\n");
String comment = parseComment(p.inlineTags());
if (comment.length() > 0) {
buf.append(comment).append("\n\n");
}
buildPackageClassesPage(p);
}
}
writeFile(moduleName + ".textile", buf.toString());
}
private String buildTypeLink(Type type) {
if (type.isPrimitive()) {
return type.simpleTypeName();
} else {
if (type.qualifiedTypeName().startsWith("java")) {
return "[" + type.simpleTypeName() + "|" + JAVA_API_URL
+ type.qualifiedTypeName().replaceAll("\\.", "/")
+ ".html]";
} else {
return "[" + type.simpleTypeName() + "|"
+ type.qualifiedTypeName() + "]";
}
}
}
private String parseComment(Tag[] tags) {
StringBuffer buf = new StringBuffer();
for (Tag t : tags) {
String type = t.name();
if (type.equalsIgnoreCase("text")) {
buf.append(t.text());
} else if (type.equalsIgnoreCase("@link")) {
SeeTag st = (SeeTag) t;
buf.append('[');
if (st.label() != null && st.label().length() > 0) {
buf.append(st.label()).append('|');
}
buf.append(st.referencedClassName());
if (st.referencedMemberName() != null) {
buf.append('#').append(st.referencedMemberName());
}
buf.append("]");
}
// TODO support other tags like @see, etc.
}
String comment = buf.toString().replaceAll("\n", "");
comment = comment.replaceAll("\\<.*?\\>", "");
return comment;
}
private String parseMembers(ExecutableMemberDoc[] mems) {
StringBuffer buf = new StringBuffer();
for (ExecutableMemberDoc m : mems) {
ParamTag[] params = m.paramTags();
if (m instanceof MethodDoc) {
MethodDoc md = (MethodDoc) m;
buf.append("{anchor:").append(md.name())
.append(md.flatSignature()).append("}\n");
}
buf.append("h3. " + m.qualifiedName());
buf.append('(');
for (int i = 0, nump = m.parameters().length; i < nump; i++) {
Parameter p = m.parameters()[i];
buf.append(buildTypeLink(p.type())).append(" ")
.append(p.name());
if (i < nump - 1) {
buf.append(", ");
}
}
buf.append(")\n\n");
String comment = parseComment(m.inlineTags());
if (comment.length() > 0) {
buf.append(comment).append("\n\n");
}
for (ParamTag p : params) {
buf.append(
"* *" + p.parameterName() + "*: "
+ p.parameterComment()).append("\n");
}
if (m instanceof MethodDoc) {
MethodDoc md = (MethodDoc) m;
buf.append("\n*Returns:* ")
.append(md.returnType().qualifiedTypeName())
.append("\n");
}
buf.append("\n");
}
return buf.toString();
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public void setTargetDir(String targetDir) {
this.targetDir = targetDir;
}
private void writeFile(String fileName, String buf) {
try {
File path = new File(targetDir + "/" + fileName);
System.out.println("writing: " + path.getAbsolutePath());
PrintWriter writer = FileUtils.createWriter(path);
FileUtils.saveText(writer, buf);
} catch (IOException e) {
e.printStackTrace();
}
}
}