/*
* Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.xmldoclet;
import com.sun.javadoc.*;
import org.visage.tools.code.FunctionType;
import org.visage.tools.xslhtml.XHTMLProcessingUtils;
import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.sax.*;
/**
* Javadoc doclet which generates XML output.
* @author tball
*/
public class XMLDoclet {
private PrintWriter out;
private Transformer serializer;
private TransformerHandler hd;
private AttributesImpl attrs;
// option values
private static String outFileName = null;
private static List<String> xmlFiles = new ArrayList<String>();
// paths from which we would load soure files and "doc-files" to copy
private static String sourcePath;
private static File outDocsDir = new File("visagedocs");
private static boolean includeAuthorTags = false;
private static boolean includeDeprecatedTags = true;
private static boolean includeSinceTags = true;
private static boolean includeVersionTags = false;
private static boolean processXSLT = true;
private static ResourceBundle messageRB = null;
private static String xsltFileName = null;
private static final boolean debug = false;
private static final Map<String,String> params = new HashMap<String, String>();
static final Option[] options = {
new Option("-o", getString("out.file.option"), getString("out.file.description")),
new Option("-version", getString("version.description")),
new Option("-author", getString("author.description")),
new Option("-nosince", getString("nosince.description")),
new Option("-nodeprecated", getString("nodeprecated.description")),
new Option("-nohtml", getString("nohtml.description")),
new Option("-xsltfile", getString("out.file.option"), getString("xsltfile.description")),
new Option("-mastercss", getString("out.file.option"), getString("xsltfile.description")),
new Option("-extracss", getString("out.file.option"), getString("xsltfile.description")),
new Option("-extrajs", getString("out.file.option"), getString("xsltfile.description")),
new Option("-extrajs2", getString("out.file.option"), getString("xsltfile.description")),
new Option("-xsl:", getString("xslproperty.description"), "name=value"),
new Option("-d", getString("out.dir.option"), getString("out.dir.description")),
new Option("-i", getString("in.file.option"), getString("in.file.description"))
};
/**
* Generate documentation here.
* This method is required for all doclets.
*
* @return true on success.
*/
public static boolean start(RootDoc root) {
try {
XMLDoclet doclet = new XMLDoclet();
doclet.generateXML(root);
if(processXSLT) {
FileInputStream xsltStream = xsltFileName != null ?
new FileInputStream(xsltFileName) : null;
XHTMLProcessingUtils.process(xmlFiles, xsltStream, sourcePath, outDocsDir, params);
} else {
/*
* We are just generating XML files. But, we would still like to copy
* "doc-files" subdirectories in location where we generate intermediate
* XML files. In a subsequent run where XML files are specified as inputs
* we can copy "doc-files" to right place along with output HTMLs.
*/
PackageDoc[] pkgs = doclet.packagesToProcess(root);
String[] pkgNames = new String[pkgs.length];
for (int index = 0; index < pkgs.length; index++) {
pkgNames[index] = pkgs[index].name();
}
Util.copyDocFiles(pkgNames, sourcePath, new File(outFileName).getParentFile());
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Check for doclet-added options. Returns the number of
* arguments you must specify on the command line for the
* given option. For example, "-d docs" would return 2.
* <P>
* This method is required if the doclet contains any options.
* If this method is missing, Javadoc will print an invalid flag
* error for every option.
*
* @return number of arguments on the command line for an option
* including the option name itself. Zero return means
* option not known. Negative value means error occurred.
*/
public static int optionLength(String option) {
if (option.equals("-help")) {
System.out.println(getString("help.header"));
for (Option o : options)
System.out.println(o.help());
return 1;
}
for (Option o : options) {
if (o.name().equals(option))
return o.length();
}
return 0; // default is option unknown
}
/**
* Check that options have the correct arguments.
* Printing option related error messages (using the provided
* DocErrorReporter) is the responsibility of this method.
*
* @return true if the options are valid.
*/
public static boolean validOptions(String options[][],
DocErrorReporter reporter) {
if (debug) {
for(int i=0; i< options.length; i++) {
for(int j=0; j<options[i].length; j++) {
System.out.println("got: " + options[i][j]);
}
}
}
List<String> otherInputs = new ArrayList<String>();
for (String[] option : options) {
if (option[0].equals("-sourcepath")) {
sourcePath = option[1];
} else if (option[0].equals("-o"))
outFileName = option[1];
else if (option[0].equals("-i"))
otherInputs.add(option[1]);
else if (option[0].equals("-version"))
includeVersionTags = true;
else if (option[0].equals("-author"))
includeAuthorTags = true;
else if (option[0].equals("-nosince"))
includeSinceTags = false;
else if (option[0].equals("-nodeprecated"))
includeDeprecatedTags = false;
else if (option[0].equals("-nohtml"))
processXSLT = false;
else if (option[0].equals("-xsltfile"))
xsltFileName = option[1];
else if (option[0].equals("-mastercss"))
params.put("master-css",option[1]);
else if (option[0].equals("-extracss"))
params.put("extra-css",option[1]);
else if (option[0].equals("-extrajs"))
params.put("extra-js",option[1]);
else if (option[0].equals("-extrajs2"))
params.put("extra-js2",option[1]);
else if (option[0].equals("-d"))
outDocsDir = new File(option[1]);
else if (option[0].startsWith("-xsl:")) {
String s = option[1];
int i = s.indexOf('=');
if (i == -1)
return false;
String name = s.substring(0, i);
String value = s.substring(i+1);
System.out.println("using a custom XSL parameter: '" + name + "'='" + value +"'");
params.put(name, value);
}
}
if (outFileName == null) {
try {
File f = File.createTempFile("javadoc", ".xml");
outFileName = f.getPath();
} catch (IOException ex) {
Logger.getLogger(XMLDoclet.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
xmlFiles.add(outFileName);
xmlFiles.addAll(otherInputs);
if (sourcePath == null) {
Set<String> inputDirs = new HashSet<String>();
for (String in : otherInputs) {
inputDirs.add(new File(in).getParent());
}
StringBuilder buf = new StringBuilder();
for (String in : inputDirs) {
buf.append(in);
buf.append(File.pathSeparatorChar);
}
sourcePath = buf.toString();
if (sourcePath.length() == 0) {
sourcePath = ".";
}
}
return true;
}
private PackageDoc[] packagesToProcess(RootDoc root) {
Set<PackageDoc> set = new HashSet<PackageDoc>(Arrays.asList(root.specifiedPackages()));
ClassDoc[] classes = root.specifiedClasses();
for (int i = 0; i < classes.length; i++) {
set.add(classes[i].containingPackage());
}
ArrayList<PackageDoc> results = new ArrayList<PackageDoc>(set);
Collections.sort(results);
return results.toArray(new PackageDoc[] {});
}
/**
* Return the version of the Java Programming Language supported
* by this doclet.
*
* @return the language version supported by this doclet.
*/
public static LanguageVersion languageVersion() {
return LanguageVersion.JAVA_1_5;
}
void generateXML(RootDoc root) throws IOException, TransformerException, SAXException {
initTransformer();
attrs.clear();
hd.startElement("", "", "javadoc", attrs);
generateComment(root);
for (PackageDoc pkg : packagesToProcess(root))
generatePackage(pkg);
hd.endElement("","","javadoc");
hd.endDocument();
}
private void generateExecutableMember(ExecutableMemberDoc exec, String kind) throws SAXException {
if (!exec.isSynthetic()) {
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", exec.name());
attrs.addAttribute("", "", "qualifiedName", "CDATA", exec.qualifiedName());
if (exec instanceof MethodDoc)
attrs.addAttribute("", "", "varargs", "CDATA", Boolean.toString(exec.isVarArgs()));
hd.startElement("", "", kind, attrs);
generateComment(exec);
generateAnnotations(exec.annotations());
generateModifiers(exec);
generateTypeParameters(exec.typeParameters());
generateParameters(exec.parameters(),exec);
Type[] exceptions = exec.thrownExceptionTypes();
if (exceptions.length > 0) {
attrs.clear();
hd.startElement("", "", "thrownExceptions", attrs);
for (Type t : exceptions)
generateTypeRef(t, "exception", null);
hd.endElement("", "", "thrownExceptions");
}
if (exec instanceof MethodDoc) {
MethodDoc m = (MethodDoc)exec;
generateTypeRef(m.returnType(), "returns", rawReturnType(m));
MethodDoc overridden = m.overriddenMethod();
if (overridden != null) {
String name = overridden.qualifiedName();
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", name);
hd.startElement("", "", "overrides", attrs);
hd.endElement("", "", "overrides");
}
}
hd.endElement("", "", kind);
}
}
private void generateField(FieldDoc field, String kind) throws SAXException {
if (!field.isSynthetic()) {
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", field.name());
attrs.addAttribute("", "", "qualifiedName", "CDATA", field.qualifiedName());
attrs.addAttribute("", "", "enumConstant", "CDATA", Boolean.toString(field.isEnumConstant()));
hd.startElement("", "", kind, attrs);
generateComment(field);
generateAnnotations(field.annotations());
generateModifiers(field);
generateTypeRef(field.type(), "type", rawType(field));
String constantValue = field.constantValueExpression();
if (constantValue != null) {
attrs.clear();
attrs.addAttribute("", "", "value", "CDATA", constantValue);
hd.startElement("", "", "constant", attrs);
hd.endElement("", "", "constant");
}
hd.endElement("", "", kind);
}
}
private void generateFullHierarchy(ClassDoc cls) throws SAXException {
attrs.clear();
hd.startElement("", "", "hierarchy", attrs);
//return a list of inherited types, filtering out duplicates
//and preserving the in-order traversal
List<Type> types = findInheritedTypes(cls);
Set<Type> uniqueTypes = new HashSet<Type>();
ListIterator<Type> it = types.listIterator();
while(it.hasNext()) {
Type type = it.next();
if(uniqueTypes.contains(type)) {
it.remove();
} else {
uniqueTypes.add(type);
}
}
//generate xml for the final list of inherited types
for (Type intf : types) {
generateTypeRef(intf, "super", null);
}
hd.endElement("", "", "hierarchy");
}
private List<Type> findInheritedTypes(ClassDoc cls) {
List<Type> types = new ArrayList<Type>();
for (Type type : cls.interfaces()) {
if (type != null) {
types.add(type);
ClassDoc cd = type.asClassDoc();
types.addAll(findInheritedTypes(cd));
}
}
return types;
}
private void generatePackage(PackageDoc pkg) throws SAXException {
ClassDoc[] allClasses = pkg.allClasses();
if (allClasses.length == 0)
return;
// TODO: call containingPackage() on a class in this package,
// before processing this package. This has the side effect of
// calling PackageDocImpl.setDocPath() on this package, which is
// necessary in order for this package's doc to be processed.
// If setDocPath() hasn't been called by the time generateComment()
// is called, the package doc will be missing.
allClasses[0].containingPackage();
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", pkg.name());
hd.startElement("", "", "package", attrs);
generateComment(pkg);
generateAnnotations(pkg.annotations());
for (ClassDoc cls : pkg.allClasses())
generateClass(cls);
hd.endElement("", "", "package");
}
private void generateClass(ClassDoc cls) throws SAXException {
/**
* Visage generates class for modules too. So, a "package-info.visage" will
* result in a class. Because we have captured package level doc comment
* we can ignore this class. Without this "package-info" will appear in
* classes list!
*/
if (cls.simpleTypeName().equals("package-info")) {
return;
}
boolean visageClass = isVisageClass(cls);
String classType =
cls.isAnnotationType() ? "annotation" :
cls.isEnum() ? "enum" :
cls.isInterface() ? "interface" :
cls.isAbstract() ? "abstractClass" :
"class";
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", cls.name());
attrs.addAttribute("", "", "qualifiedName", "CDATA", cls.qualifiedName());
attrs.addAttribute("", "", "packageName", "CDATA", cls.containingPackage().name());
ClassDoc containingClass = cls.containingClass();
if (containingClass != null) {
attrs.addAttribute("", "", "outerClass", "CDATA", containingClass.qualifiedName());
}
attrs.addAttribute("", "", "language", "CDATA", visageClass ? "visage" : "java");
attrs.addAttribute("", "", "classType","CDATA",classType);
hd.startElement("", "", "class", attrs);
generateComment(cls);
generateModifiers(cls);
if (!visageClass) {
generateAnnotations(cls.annotations());
generateTypeParameters(cls.typeParameters());
}
if (cls.superclass() != null && !cls.superclass().qualifiedName().equals("java.lang.Object")) {
generateTypeRef(cls.superclass(), "superclass", null);
}
attrs.clear();
hd.startElement("", "", "interfaces", attrs);
for (Type intf : cls.interfaces())
generateTypeRef(intf, "interface", null);
hd.endElement("", "", "interfaces");
generateFullHierarchy(cls);
if (!visageClass) {
for (ConstructorDoc cons : cls.constructors())
generateExecutableMember(cons, "constructor");
}
for (MethodDoc meth : cls.methods()) {
if (visageClass) {
generateExecutableMember(meth, meth.isStatic()?
"script-function" : "function");
} else {
generateExecutableMember(meth, "method");
}
}
for (FieldDoc field : cls.fields()) {
if (visageClass) {
generateField(field, field.isStatic()?
"script-var" : "var");
} else {
generateField(field, "field");
}
}
for (FieldDoc field : cls.enumConstants())
generateField(field, visageClass ? "script-var" : "field");
hd.endElement("", "", "class");
}
private void generateModifiers(ProgramElementDoc element) throws SAXException {
attrs.clear();
boolean bound = isBoundFunction(element);
String modifiersText = element.modifiers();
attrs.addAttribute("", "", "text", "CDATA", modifiersText);
hd.startElement("", "", "modifiers", attrs);
attrs.clear();
if (element.isPublic()) {
hd.startElement("", "", "public", attrs);
hd.endElement("", "", "public");
}
else if (element.isProtected()) {
hd.startElement("", "", "protected", attrs);
hd.endElement("", "", "protected");
}
else if (element.isPackagePrivate()) {
hd.startElement("", "", "package", attrs);
hd.endElement("", "", "package");
}
else if (element.isPrivate()) {
hd.startElement("", "", "private", attrs);
hd.endElement("", "", "private");
}
else {
hd.startElement("", "", "script-private", attrs);
hd.endElement("", "", "script-private");
}
if (element.isStatic()) {
hd.startElement("", "", "static", attrs);
hd.endElement("", "", "static");
}
if (element.isFinal()) {
hd.startElement("", "", "final", attrs);
hd.endElement("", "", "final");
}
if (isPublicRead(element)) {
hd.startElement("", "", "public-read", attrs);
hd.endElement("", "", "public-read");
}
if (isPublicInit(element)) {
hd.startElement("", "", "public-init", attrs);
hd.endElement("", "", "public-init");
}
if (isAbstract(element)) {
hd.startElement("", "", "abstract", attrs);
hd.endElement("", "", "abstract");
}
if (isDef(element)) { // not sure how we want to document this
hd.startElement("", "", "read-only", attrs);
hd.endElement("", "", "read-only");
}
if (element instanceof ClassDoc &&
isMixin((ClassDoc)element)) {
hd.startElement("", "", "mixin", attrs);
hd.endElement("", "", "mixin");
}
/***
if (element.isNative()) {
hd.startElement("", "", "native", attrs);
hd.endElement("", "", "native");
}
if (element.isStrict()) {
hd.startElement("", "", "strictfp", attrs);
hd.endElement("", "", "strictfp");
}
**/
if (bound) {
hd.startElement("", "", "bound", attrs);
hd.endElement("", "", "bound");
}
hd.endElement("", "", "modifiers");
}
private void generateParameters(Parameter[] parameters, ProgramElementDoc doc) throws SAXException {
attrs.clear();
hd.startElement("", "", "parameters", attrs);
List<Tag> paramDocs = new ArrayList<Tag>();
for(Tag t : doc.tags()) {
if("@param".equals(t.kind())) {
paramDocs.add(t);
}
}
for(int i=0; i<parameters.length; i++) {
Parameter p = parameters[i];
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", p.name());
hd.startElement("", "", "parameter", attrs);
generateTypeRef(p.type(), "type", rawType(p));
generateAnnotations(p.annotations());
attrs.clear();
hd.startElement("","", "docComment", attrs);
if(paramDocs.size() > i) {
Tag t = paramDocs.get(i);
Tag[] inlineTags = t.inlineTags();
if (inlineTags.length <= 1) {
String text = t.text();
text = text.trim();
//trim off the name of the variable, if present
int n = text.indexOf(" ");
if(n > 0) {
text = text.substring(n);
}
hd.characters(text.toCharArray(), 0, text.length());
} else {
generateTags(inlineTags, "inlineTags");
}
}
hd.endElement("", "", "docComment");
hd.endElement("", "", "parameter");
}
hd.endElement("", "", "parameters");
}
private void generateTypeParameters(TypeVariable[] typeParameters) throws SAXException {
if (typeParameters.length > 0) {
attrs.clear();
hd.startElement("", "", "typeParameters", attrs);
for (TypeVariable tp : typeParameters) {
attrs.clear();
attrs.addAttribute("", "", "typeName", "CDATA", tp.typeName());
attrs.addAttribute("", "", "simpleTypeName", "CDATA", tp.simpleTypeName());
attrs.addAttribute("", "", "qualifiedTypeName", "CDATA", tp.qualifiedTypeName());
hd.startElement("", "", "typeParameter", attrs);
hd.endElement("", "", "typeParameter");
}
hd.endElement("", "", "typeParameters");
}
}
private void generateTypeRef(Type type, String kind,
com.sun.tools.mjavac.code.Type rawType) throws SAXException {
if (type != null) {
attrs.clear();
ClassDoc cd = type.asClassDoc();
boolean isSequence = false;
if (cd != null) {
isSequence = isSequence(cd);
if (isSequence) {
if (rawType == null)
throw new AssertionError("unknown sequence type");
type = sequenceType(cd, rawType);
cd = type.asClassDoc();
}
}
boolean isFunctionType = rawType instanceof FunctionType;
String simpleName = isFunctionType ?
simpleFunctionalTypeName(cd, rawType) : type.simpleTypeName();
attrs.addAttribute("", "", "typeName", "CDATA", type.typeName());
attrs.addAttribute("", "", "simpleTypeName", "CDATA", simpleName);
attrs.addAttribute("", "", "qualifiedTypeName", "CDATA", type.qualifiedTypeName());
if(cd != null && !type.isPrimitive()) {
attrs.addAttribute("", "", "packageName", "CDATA", cd.containingPackage().name());
}
attrs.addAttribute("", "", "dimension", "CDATA", type.dimension());
attrs.addAttribute("", "", "toString", "CDATA", type.qualifiedTypeName() + type.dimension());
attrs.addAttribute("", "", "sequence", "CDATA", Boolean.toString(isSequence));
attrs.addAttribute("", "", "functionType", "CDATA", Boolean.toString(isFunctionType));
hd.startElement("", "", kind, attrs);
hd.endElement("", "", kind);
}
}
private void generateComment(Doc doc) throws SAXException {
String rawCommentText = doc.getRawCommentText();
if (rawCommentText.length() > 0) {
attrs.clear();
hd.startElement("", "", "docComment", attrs);
hd.startElement("", "", "rawCommentText", attrs);
hd.characters(rawCommentText.toCharArray(), 0, rawCommentText.length());
hd.endElement("", "", "rawCommentText");
String commentText = doc.commentText();
hd.startElement("", "", "commentText", attrs);
hd.characters(commentText.toCharArray(), 0, commentText.length());
hd.endElement("", "", "commentText");
generateTags(doc.tags(), "tags");
Tag[] firstSentenceTags = doc.firstSentenceTags();
generateTags(firstSentenceTags, "firstSentenceTags");
generateTags(doc.seeTags(), "seeTags");
Tag[] inlineTags = getInlineTags(doc);
generateTags(inlineTags, "inlineTags");
boolean multipleSentences = false;
if(inlineTags.length != firstSentenceTags.length) {
multipleSentences = true;
} else {
for(int i=0; i<firstSentenceTags.length; i++) {
if(!firstSentenceTags[i].text().equals(inlineTags[i].text())) {
multipleSentences = true;
}
}
}
attrs.clear();
attrs.addAttribute("", "", "multipleSentences", "CDATA", Boolean.toString(multipleSentences));
hd.startElement("", "", "extraNotes", attrs);
hd.endElement("", "", "extraNotes");
hd.endElement("", "", "docComment");
}
}
private void generateTags(Tag[] tags, String tagKind) throws SAXException, SAXException {
if (tags.length == 0)
return;
attrs.clear();
hd.startElement("", "", tagKind, attrs);
for (Tag t : tags) {
String kind = t.kind();
if (kind.startsWith("@"))
kind = kind.substring(1);
if (kind.equals("@author") && !includeAuthorTags)
continue;
if (kind.equals("@deprecated") && !includeDeprecatedTags)
continue;
if (kind.equals("@since") && !includeSinceTags)
continue;
if (kind.equals("@version") && !includeVersionTags)
continue;
if (!kind.matches("\\w+")) {
System.out.println("possible invalid tag kind: " + kind);
kind = "invalidtag";
}
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", t.name());
String label = null;
//process see, link tags specially
if("@see".equals(t.name()) || "@link".equals(t.name())) {
String href = t.text();
if(t instanceof SeeTag) {
SeeTag see = (SeeTag) t;
if(see.referencedClass() != null) {
href = "../"
+see.referencedClass().containingPackage().name()
+"/"
+see.referencedClassName()+".html";
MemberDoc referencedMember = see.referencedMember();
if (referencedMember != null) {
if (referencedMember instanceof ExecutableMemberDoc) {
ExecutableMemberDoc execMember =(ExecutableMemberDoc)referencedMember;
href += "#" + execMember.name() + execMember.signature();
} else {
href += "#"+ see.referencedMemberName();
}
}
}
label = see.label();
}
if (label == null || label.length() == 0) {
label = t.text();
}
//p("final href = " + href);
attrs.addAttribute("", "", "href", "CDATA", href);
if(label.startsWith("#")) {
label = label.substring(1);
}
attrs.addAttribute("", "", "label", "CDATA", label);
}
boolean isThrows = false;
if ("@throws".equals(t.name())) {
isThrows = true;
ThrowsTag tt = (ThrowsTag)t;
attrs.addAttribute("", "", "exceptionName", "CDATA", tt.exceptionName());
}
hd.startElement("", "", kind, attrs);
if (isThrows) {
ThrowsTag tt = (ThrowsTag)t;
generateTypeRef(tt.exceptionType(), "exception", null);
hd.startElement("", "", "comment", null);
String comment = tt.exceptionComment();
hd.characters(comment.toCharArray(), 0, comment.length());
hd.endElement("", "", "comment");
}
Tag[] inlineTags = t.inlineTags();
if (inlineTags.length <= 1) {
// for @throws we have already collected everything..
if (! isThrows) {
String text = t.text();
hd.characters(text.toCharArray(), 0, text.length());
}
} else {
generateTags(inlineTags, "inlineTags");
}
hd.endElement("", "", kind);
}
hd.endElement("", "", tagKind);
}
private Tag[] getInlineTags(Doc doc) throws SAXException, SAXException {
List<Tag> list = new ArrayList<Tag>();
Tag[] inlineTags = doc.inlineTags();
if (inlineTags.length > 0 && doc.isMethod()) { // inheritDoc tag only valid for methods
list.addAll(Arrays.asList(inlineTags));
boolean changed = false;
for(int i=0; i<list.size(); i++) {
Tag t = list.get(i);
if(t.kind().matches("@inheritDoc")) {
Doc inherited = getInheritedDoc(doc);
if (inherited != null) {
list.remove(i);
list.addAll(i,Arrays.asList(getInlineTags(inherited)));
changed = true;
}
break;
}
}
if (changed)
inlineTags = list.toArray(new Tag[0]);
}
return inlineTags;
}
private void generateAnnotations(AnnotationDesc[] annotations) throws SAXException {
if (annotations.length > 0) {
attrs.clear();
hd.startElement("", "", "annotations", attrs);
for (AnnotationDesc desc : annotations) {
attrs.clear();
hd.startElement("", "", "annotationType", attrs);
AnnotationTypeDoc type = desc.annotationType();
attrs.addAttribute("", "", "simpleName", "CDATA", type.simpleTypeName());
attrs.addAttribute("", "", "qualifiedName", "CDATA", type.qualifiedTypeName());
attrs.addAttribute("", "", "dimension", "CDATA", type.dimension());
hd.startElement("", "", type.typeName(), attrs);
AnnotationTypeElementDoc[] elements = type.elements();
for (AnnotationTypeElementDoc element : elements) {
if (!element.isSynthetic()) {
attrs.clear();
attrs.addAttribute("", "", "name", "CDATA", element.name());
attrs.addAttribute("", "", "qualifiedName", "CDATA", element.qualifiedName());
attrs.addAttribute("", "", "commentText", "CDATA", element.commentText());
AnnotationValue defValue = element.defaultValue();
if (defValue != null)
attrs.addAttribute("", "", "defaultValue", "CDATA", defValue.toString());
hd.startElement("", "", "element", attrs);
hd.endElement("", "", "element");
}
}
hd.endElement("", "", type.typeName());
hd.endElement("", "", "annotationType");
}
hd.endElement("", "", "annotations");
}
}
private Doc getInheritedDoc(Doc doc) {
if(doc instanceof MethodDoc) {
return getOverriddenMethod((MethodDoc)doc);
}
return null;
}
private MethodDoc getOverriddenMethod(MethodDoc doc) {
ClassDoc cls = doc.containingClass();
ClassDoc scls = cls.superclass();
MethodDoc meth = findDeclaredMethod(scls, doc);
if (meth == null && isVisageClass(cls)) {
for (ClassDoc intf : cls.interfaces()) {
meth = findDeclaredMethod(intf, doc);
if (meth != null)
break;
}
}
return meth;
}
private MethodDoc findDeclaredMethod(ClassDoc scls, MethodDoc doc) {
MethodDoc[] meths = scls.methods();
for (MethodDoc md : meths) {
if (md.name().equals(doc.name()) && md.signature().equals(doc.signature())) {
return md;
}
}
return null;
}
private void initTransformer() throws IOException, SAXException, TransformerException {
SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
tf.setAttribute("indent-number", new Integer(3));
hd = tf.newTransformerHandler();
serializer = hd.getTransformer();
serializer.setOutputProperty(OutputKeys.METHOD, "xml");
serializer.setOutputProperty(OutputKeys.VERSION, "1.0");
serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
//TODO: serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,"???.dtd");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
File f = new File(outFileName);
f.getParentFile().mkdirs();
out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
StreamResult streamResult = new StreamResult(out);
attrs = new AttributesImpl();
hd.setResult(streamResult);
hd.startDocument();
}
static String getString(String key) {
ResourceBundle msgRB = messageRB;
if (msgRB == null) {
try {
messageRB = msgRB =
ResourceBundle.getBundle("org.visage.tools.xmldoclet.resources.xmldoclet");
} catch (MissingResourceException e) {
throw new Error("Fatal: Resource for visagedoc is missing");
}
}
return msgRB.getString(key);
}
private static boolean isVisageClass(ClassDoc clsDoc) {
return probe(clsDoc, "isVisageClass");
}
private static boolean isMixin(ClassDoc clsDoc) {
return probe(clsDoc, "isMixin");
}
private static boolean isSequence(ClassDoc clsDoc) {
return probe(clsDoc, "isSequence");
}
private static boolean probe(ClassDoc clsDoc, String method) {
try {
Class<?> cls = clsDoc.getClass();
Method m = cls.getDeclaredMethod(method);
Object result = m.invoke(clsDoc);
return ((Boolean)result).booleanValue();
} catch (Exception e) {
return false;
}
}
private static com.sun.tools.mjavac.code.Type rawType(FieldDoc field) {
return rawType(field, "rawType");
}
private static com.sun.tools.mjavac.code.Type rawType(Parameter param) {
return rawType(param, "rawType");
}
private static com.sun.tools.mjavac.code.Type rawReturnType(MethodDoc method) {
return rawType(method, "rawReturnType");
}
private static com.sun.tools.mjavac.code.Type rawType(Object o, String method) {
try {
Class<?> cls = o.getClass();
Method m = cls.getDeclaredMethod(method);
Object result = m.invoke(o);
return (com.sun.tools.mjavac.code.Type)result;
} catch (Exception e) {
return null;
}
}
private boolean getBooleanFlag(ProgramElementDoc doc, String flagMethod) {
try {
Class<?> cls = doc.getClass();
Method m = cls.getMethod(flagMethod);
return (Boolean)m.invoke(doc);
} catch (Exception e) {
return false;
}
}
private boolean isBoundFunction(ProgramElementDoc doc) {
if (!(doc instanceof ExecutableMemberDoc))
return false;
return getBooleanFlag(doc, "isBound");
}
private boolean isScriptPrivate(ProgramElementDoc doc) {
return getBooleanFlag(doc, "isScriptPrivate");
}
private boolean isPublicInit(ProgramElementDoc doc) {
if (!(doc instanceof FieldDoc))
return false;
return getBooleanFlag(doc, "isPublicInit");
}
private boolean isPublicRead(ProgramElementDoc doc) {
if (!(doc instanceof FieldDoc))
return false;
return getBooleanFlag(doc, "isPublicRead");
}
private boolean isDef(ProgramElementDoc doc) {
if (!(doc instanceof FieldDoc))
return false;
return getBooleanFlag(doc, "isDef");
}
private boolean isAbstract(ProgramElementDoc doc) {
return getBooleanFlag(doc, "isAbstract");
}
private static Type sequenceType(ClassDoc cd, com.sun.tools.mjavac.code.Type rawType) {
try {
Class<?> cls = cd.getClass();
Method m = cls.getDeclaredMethod("sequenceType", com.sun.tools.mjavac.code.Type.class);
final Type result = (Type)m.invoke(cd, (Object)rawType);
return new Type() {
public String typeName() {
return result.typeName();
}
public String qualifiedTypeName() {
return result.qualifiedTypeName();
}
public String simpleTypeName() {
return result.simpleTypeName();
}
public String dimension() {
return "[]";
}
public boolean isPrimitive() {
return result.isPrimitive();
}
public ClassDoc asClassDoc() {
return result.asClassDoc();
}
public ParameterizedType asParameterizedType() {
return null;
}
public TypeVariable asTypeVariable() {
return null;
}
public WildcardType asWildcardType() {
return null;
}
public AnnotationTypeDoc asAnnotationTypeDoc() {
return null;
}
};
} catch (Exception e) {
return null;
}
}
private static String simpleFunctionalTypeName(ClassDoc cd, com.sun.tools.mjavac.code.Type rawType) {
try {
Class<?> cls = cd.getClass();
Method m = cls.getDeclaredMethod("simpleFunctionalTypeName", com.sun.tools.mjavac.code.Type.class);
Object result = m.invoke(cd, (Object)rawType);
return (String)result;
} catch (Exception e) {
return null;
}
}
static class Option {
String[] fields;
String help;
private static final int DESCRIPTION_COLUMN =
Integer.valueOf(getString("help.description.column"));
Option(String field, String help) {
fields = new String[] { field };
this.help = help;
}
Option(String field, String param, String help) {
fields = new String[] { field, param };
this.help = help;
}
int length() {
return fields.length;
}
String name() {
return fields[0];
}
String description() {
return fields.length == 1 ? fields[0] : fields[0] + ' ' + fields[1];
}
String help() {
StringBuffer sb = new StringBuffer(description());
while (sb.length() < DESCRIPTION_COLUMN)
sb.append(' ');
sb.append(help);
return sb.toString();
}
}
}