/*
* Copyright (c) 2005, Rob Gordon.
*/
package org.oddjob.doclet;
import org.oddjob.arooa.beandocs.BeanDoc;
import org.oddjob.arooa.beandocs.WriteableBeanDoc;
import org.oddjob.arooa.beandocs.WriteableExampleDoc;
import org.oddjob.arooa.beandocs.WriteablePropertyDoc;
import org.oddjob.tools.includes.CompositeLoader;
import org.oddjob.tools.includes.IncludeLoader;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.SeeTag;
import com.sun.javadoc.Tag;
/**
* A Processor is capable of processing a java ClassDoc object into
* a reference PageData object.
*
* @author Rob Gordon.
*/
public class Processor implements CustomTagNames {
private final ClassDoc classDoc;
private final String rootDir;
private final JobsAndTypes jats;
/**
* Create a processor.
*
* @param jats The JobsAndTypes object to lookup up link names
* in.
* @param classDoc The classdoc the processor will process.
*/
public Processor(JobsAndTypes jats, ClassDoc classDoc) {
this.jats = jats;
this.classDoc = classDoc;
String[] packages = classDoc.containingPackage().name().split(("[.]"));
String rootDir = "";
for (int i = 0; i < packages.length; ++i) {
rootDir = rootDir + (i == 0 ? "" : "/") + "..";
}
this.rootDir = rootDir;
}
/**
* Process a single text tag from an array. This is for a property
* or element tag which is expected to be a single piece of text.
* Errors are produced if it's not.
*
* @param tags To process. There is expected to be either 0 or
* 1 tag. An error will be written to stderr if there is more.
* @param replacement The text to use if the tags are empty.
*
* @return The text to use. Null if there was no tags.
*/
String processSingleTextTag(Tag[] tags, String replacement) {
if (tags.length == 0) {
return null;
}
if (tags.length > 1) {
System.err.println("More than one tag for [" + replacement
+ "]. Ignoring all others");
}
String text = tags[0].text();
if (text == null || text.trim().equals("")) {
return replacement;
}
return text;
}
/**
* Process fields and methods
*
* @param beanDoc The job page this method will populate.
* @param doc The field or method doc object.
*/
void processFieldOrMethod(WriteableBeanDoc beanDoc, Doc doc) {
Tag[] propertyTags = doc.tags(PROPERTY_TAG);
String propertyText = processSingleTextTag(
propertyTags, doc.name());
if (propertyText == null) {
// no property tag then ignore any other tags.
return;
}
WriteablePropertyDoc prop = beanDoc.propertyDocFor(propertyText);
if (prop == null) {
System.err.println("No such property: " + propertyText);
return;
}
Tag[] descriptionTags = doc.tags(DESCRIPTION_TAG);
prop.setAllText(processMajorTags(descriptionTags));
prop.setFirstSentence(processFirstLine(
descriptionTags, this.rootDir));
Tag[] rtags = doc.tags(REQUIRED_TAG);
if (rtags.length != 0) {
prop.setRequired(rtags[0].text());
}
}
/**
* Process inline tags. Creates a link if appropriate.
*
* @param inlines The inline tags.
* @return The processes text.
*/
String processInlineTags(Tag[] inlines, String rootDir) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < inlines.length; ++i) {
Tag inlineTag = inlines[i];
if (inlineTag instanceof SeeTag) {
SeeTag seeTag = (SeeTag) inlineTag;
String ref = seeTag.referencedClassName();
String fileName = ref.replace('.', '/') + ".html";
BeanDoc beanDoc = jats.docFor(ref);
if (beanDoc != null) {
buffer.append("<a href='" + rootDir + "/" + fileName + "'>"
+ beanDoc.getName() + "</a>");
}
else if (ref.startsWith("org.oddjob")) {
buffer.append("<code><a href='" + getApiDirFrom(rootDir) + "/" + fileName + "'>"
+ ref + "</a></code>");
}
else {
buffer.append("<code>" + ref + "</code>");
}
}
else {
IncludeLoader loader = new CompositeLoader();
if (loader.canLoad(inlineTag.name())) {
buffer.append(loader.load(inlineTag.text()));
}
else {
buffer.append(inlineTag.text());
}
}
}
return buffer.toString();
}
/**
* Process an array of tags. This is for a tag such as description.
*
* @param tags An array of tags
* @return The text equivalent.
*/
String processMajorTags(Tag[] tags) {
if (tags == null) {
return null;
}
StringBuffer result = new StringBuffer();
for (int i = 0; i < tags.length; ++i) {
result.append(processInlineTags(
tags[i].inlineTags(),
this.rootDir));
}
return result.toString();
}
/**
* Process the first line from a collection of major
* tags such as description.
*
* @param tags The major tags.
* @return The first line text.
*/
String processFirstLine(Tag[] tags, String rootDir) {
if (tags == null) {
return null;
}
if (tags.length == 0) {
return null;
}
return processInlineTags(tags[0].firstSentenceTags(),
rootDir);
}
/**
* Process all member fields and methods including superclasses.
*
* @param pageData
* @param classDoc
*/
void processAllMembers(WriteableBeanDoc pageData, ClassDoc classDoc) {
if (classDoc == null) {
return;
}
processAllMembers(pageData, classDoc.superclass());
FieldDoc[] fds = classDoc.fields();
for (int i = 0; i < fds.length; ++i) {
processFieldOrMethod(pageData, fds[i]);
}
MethodDoc[] mds = classDoc.methods();
for (int i = 0; i < mds.length; ++i) {
processFieldOrMethod(pageData, mds[i]);
}
}
/**
* Create page data for a given doclet.
*
* @param name
* @param cd
*/
public BeanDoc process() {
String fqcn = classDoc.qualifiedName();
WriteableBeanDoc beanDoc = jats.docFor(fqcn);
System.out.println("Processing: " + beanDoc.getName());
Tag[] descriptionTags = classDoc.tags(DESCRIPTION_TAG);
String firstLine = processFirstLine(descriptionTags, ".");
if (firstLine == null) {
System.err.println("No oddjob.description tag for " + fqcn);
}
beanDoc.setFirstSentence(firstLine);
beanDoc.setAllText(processMajorTags(descriptionTags));
Tag[] exampleTags = classDoc.tags(EXAMPLE_TAG);
for (int i = 0; i < exampleTags.length; ++i) {
WriteableExampleDoc exampleDoc = new WriteableExampleDoc();
exampleDoc.setAllText(
processInlineTags(exampleTags[i].inlineTags(),
this.rootDir));
exampleDoc.setFirstSentence(
processFirstLine(new Tag[] { exampleTags[i] },
this.rootDir));
beanDoc.addExampleDoc(exampleDoc);
}
processAllMembers(beanDoc, classDoc);
return beanDoc;
}
public static String fqcnFor(ClassDoc classDoc) {
return classDoc.containingPackage().name() + "." + classDoc.name();
}
private String getApiDirFrom(String rootDir) {
return rootDir + "/../api";
}
}