blob: f68a60b126e2fb3ad56b455249e71c78dd5bcbdb [file] [log] [blame]
package jdiff;
import com.sun.javadoc.*;
import com.sun.javadoc.ParameterizedType;
import com.sun.javadoc.Type;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
/**
* Converts a Javadoc RootDoc object into a representation in an
* XML file.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class RootDocToXML {
/** Default constructor. */
public RootDocToXML() {
}
/**
* Write the XML representation of the API to a file.
*
* @param root the RootDoc object passed by Javadoc
* @return true if no problems encountered
*/
public static boolean writeXML(RootDoc root) {
String tempFileName = outputFileName;
if (outputDirectory != null) {
tempFileName = outputDirectory;
if (!tempFileName.endsWith(JDiff.DIR_SEP))
tempFileName += JDiff.DIR_SEP;
tempFileName += outputFileName;
}
try {
FileOutputStream fos = new FileOutputStream(tempFileName);
outputFile = new PrintWriter(fos);
System.out.println("JDiff: writing the API to file '" + tempFileName + "'...");
if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) {
RootDocToXML apiWriter = new RootDocToXML();
apiWriter.emitXMLHeader();
apiWriter.logOptions();
apiWriter.processPackages(root);
apiWriter.emitXMLFooter();
}
outputFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + tempFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
// If validation is desired, write out the appropriate api.xsd file
// in the same directory as the XML file.
if (XMLToAPI.validateXML) {
writeXSD();
}
return true;
}
/**
* Write the XML Schema file used for validation.
*/
public static void writeXSD() {
String xsdFileName = outputFileName;
if (outputDirectory == null) {
int idx = xsdFileName.lastIndexOf('\\');
int idx2 = xsdFileName.lastIndexOf('/');
if (idx == -1 && idx2 == -1) {
xsdFileName = "";
} else if (idx == -1 && idx2 != -1) {
xsdFileName = xsdFileName.substring(0, idx2);
} else if (idx != -1 && idx2 == -1) {
xsdFileName = xsdFileName.substring(0, idx);
} else if (idx != -1 && idx2 != -1) {
int max = idx2 > idx ? idx2 : idx;
xsdFileName = xsdFileName.substring(0, max);
}
} else {
xsdFileName = outputDirectory;
if (!xsdFileName.endsWith(JDiff.DIR_SEP))
xsdFileName += JDiff.DIR_SEP;
}
xsdFileName += "api.xsd";
try {
FileOutputStream fos = new FileOutputStream(xsdFileName);
PrintWriter xsdFile = new PrintWriter(fos);
// The contents of the api.xsd file
xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
xsdFile.println("");
xsdFile.println("<xsd:annotation>");
xsdFile.println(" <xsd:documentation>");
xsdFile.println(" Schema for JDiff API representation.");
xsdFile.println(" </xsd:documentation>");
xsdFile.println("</xsd:annotation>");
xsdFile.println();
xsdFile.println("<xsd:element name=\"api\" type=\"apiType\"/>");
xsdFile.println("");
xsdFile.println("<xsd:complexType name=\"apiType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"package\" type=\"packageType\" minOccurs='1' maxOccurs='unbounded'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"packageType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:choice maxOccurs='unbounded'>");
xsdFile.println(" <xsd:element name=\"class\" type=\"classType\"/>");
xsdFile.println(" <xsd:element name=\"interface\" type=\"classType\"/>");
xsdFile.println(" </xsd:choice>");
xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"classType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"implements\" type=\"interfaceTypeName\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"constructor\" type=\"constructorType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"method\" type=\"methodType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"field\" type=\"fieldType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"extends\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"interfaceTypeName\">");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"constructorType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"paramsType\">");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"exceptionType\">");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"methodType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"param\" type=\"paramsType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"return\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"native\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"synchronized\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"fieldType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"transient\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"volatile\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"value\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("</xsd:schema>");
xsdFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + xsdFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/**
* Write the options which were used to generate this XML file
* out as XML comments.
*/
public void logOptions() {
outputFile.print("<!-- ");
outputFile.print(" Command line arguments = " + Options.cmdOptions);
outputFile.println(" -->");
}
/**
* Process each package and the classes/interfaces within it.
*
* @param pd an array of PackageDoc objects
*/
public void processPackages(RootDoc root) {
PackageDoc[] specified_pd = root.specifiedPackages();
Map pdl = new TreeMap();
for (int i = 0; specified_pd != null && i < specified_pd.length; i++) {
pdl.put(specified_pd[i].name(), specified_pd[i]);
}
// Classes may be specified separately, so merge their packages into the
// list of specified packages.
ClassDoc[] cd = root.specifiedClasses();
// This is lists of the specific classes to document
Map classesToUse = new HashMap();
for (int i = 0; cd != null && i < cd.length; i++) {
PackageDoc cpd = cd[i].containingPackage();
if (cpd == null && !packagesOnly) {
// If the RootDoc object has been created from a jar file
// this duplicates classes, so we have to be able to disable it.
// TODO this is still null?
cpd = root.packageNamed("anonymous");
}
String pkgName = cpd.name();
String className = cd[i].name();
if (trace) System.out.println("Found package " + pkgName + " for class " + className);
if (!pdl.containsKey(pkgName)) {
if (trace) System.out.println("Adding new package " + pkgName);
pdl.put(pkgName, cpd);
}
// Keep track of the specific classes to be used for this package
List classes;
if (classesToUse.containsKey(pkgName)) {
classes = (ArrayList) classesToUse.get(pkgName);
} else {
classes = new ArrayList();
}
classes.add(cd[i]);
classesToUse.put(pkgName, classes);
}
PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]);
for (int i = 0; pd != null && i < pd.length; i++) {
String pkgName = pd[i].name();
// Check for an exclude tag in the package doc block, but not
// in the package.htm[l] file.
if (!shownElement(pd[i], null))
continue;
if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName);
outputFile.println("<package name=\"" + pkgName + "\">");
int tagCount = pd[i].tags().length;
if (trace) System.out.println("#tags: " + tagCount);
List classList;
if (classesToUse.containsKey(pkgName)) {
// Use only the specified classes in the package
System.out.println("Using the specified classes");
classList = (ArrayList) classesToUse.get(pkgName);
} else {
// Use all classes in the package
classList = new LinkedList(Arrays.asList(pd[i].allClasses()));
}
Collections.sort(classList);
ClassDoc[] classes = new ClassDoc[classList.size()];
classes = (ClassDoc[])classList.toArray(classes);
processClasses(classes, pkgName);
addPkgDocumentation(root, pd[i], 2);
outputFile.println("</package>");
}
} // processPackages
/**
* Process classes and interfaces.
*
* @param cd An array of ClassDoc objects.
*/
public void processClasses(ClassDoc[] cd, String pkgName) {
if (cd.length == 0)
return;
if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length);
for (int i = 0; i < cd.length; i++) {
String className = cd[i].name();
if (trace) System.out.println("PROCESSING CLASS/IFC: " + className);
// Only save the shown elements
if (!shownElement(cd[i], classVisibilityLevel))
continue;
boolean isInterface = false;
if (cd[i].isInterface())
isInterface = true;
if (isInterface) {
outputFile.println(" <!-- start interface " + pkgName + "." + className + " -->");
outputFile.print(" <interface name=\"" + className + "\"");
} else {
outputFile.println(" <!-- start class " + pkgName + "." + className + " -->");
outputFile.print(" <class name=\"" + className + "\"");
}
// Add attributes to the class element
Type parent = cd[i].superclassType();
if (parent != null)
outputFile.println(" extends=\"" + buildEmittableTypeString(parent) + "\"");
outputFile.println(" abstract=\"" + cd[i].isAbstract() + "\"");
addCommonModifiers(cd[i], 4);
outputFile.println(">");
// Process class members. (Treat inner classes as members.)
processInterfaces(cd[i].interfaceTypes());
processConstructors(cd[i].constructors());
processMethods(cd[i], cd[i].methods());
processFields(cd[i].fields());
addDocumentation(cd[i], 4);
if (isInterface) {
outputFile.println(" </interface>");
outputFile.println(" <!-- end interface " + pkgName + "." + className + " -->");
} else {
outputFile.println(" </class>");
outputFile.println(" <!-- end class " + pkgName + "." + className + " -->");
}
// Inner classes have already been added.
/*
ClassDoc[] ic = cd[i].innerClasses();
for (int k = 0; k < ic.length; k++) {
System.out.println("Inner class " + k + ", name = " + ic[k].name());
}
*/
}//for
}//processClasses()
/**
* Add qualifiers for the program element as attributes.
*
* @param ped The given program element.
*/
public void addCommonModifiers(ProgramElementDoc ped, int indent) {
addSourcePosition(ped, indent);
// Static and final and visibility on one line
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.print("static=\"" + ped.isStatic() + "\"");
outputFile.print(" final=\"" + ped.isFinal() + "\"");
// Visibility
String visibility = null;
if (ped.isPublic())
visibility = "public";
else if (ped.isProtected())
visibility = "protected";
else if (ped.isPackagePrivate())
visibility = "package";
else if (ped.isPrivate())
visibility = "private";
outputFile.println(" visibility=\"" + visibility + "\"");
// Deprecation on its own line
for (int i = 0; i < indent; i++) outputFile.print(" ");
boolean isDeprecated = false;
Tag[] ta = ((Doc)ped).tags("deprecated");
if (ta.length != 0) {
isDeprecated = true;
}
if (ta.length > 1) {
System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only.");
System.out.println("Text is: " + ((Doc)ped).getRawCommentText());
}
if (isDeprecated) {
String text = ta[0].text(); // Use only one @deprecated tag
if (text != null && text.compareTo("") != 0) {
int idx = endOfFirstSentence(text);
if (idx == 0) {
// No useful comment
outputFile.print("deprecated=\"deprecated, no comment\"");
} else {
String fs = null;
if (idx == -1)
fs = text;
else
fs = text.substring(0, idx+1);
String st = API.hideHTMLTags(fs);
outputFile.print("deprecated=\"" + st + "\"");
}
} else {
outputFile.print("deprecated=\"deprecated, no comment\"");
}
} else {
outputFile.print("deprecated=\"not deprecated\"");
}
} //addQualifiers()
/**
* Insert the source code details, if available.
*
* @param ped The given program element.
*/
public void addSourcePosition(ProgramElementDoc ped, int indent) {
if (!addSrcInfo)
return;
if (JDiff.javaVersion.startsWith("1.1") ||
JDiff.javaVersion.startsWith("1.2") ||
JDiff.javaVersion.startsWith("1.3")) {
return; // position() only appeared in J2SE1.4
}
try {
// Could cache the method for improved performance
Class c = ProgramElementDoc.class;
Method m = c.getMethod("position", (Class[]) null);
Object sp = m.invoke(ped, (Object[]) null);
if (sp != null) {
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.println("src=\"" + sp + "\"");
}
} catch (NoSuchMethodException e2) {
System.err.println("Error: method \"position\" not found");
e2.printStackTrace();
} catch (IllegalAccessException e4) {
System.err.println("Error: class not permitted to be instantiated");
e4.printStackTrace();
} catch (InvocationTargetException e5) {
System.err.println("Error: method \"position\" could not be invoked");
e5.printStackTrace();
} catch (Exception e6) {
System.err.println("Error: ");
e6.printStackTrace();
}
}
/**
* Process the interfaces implemented by the class.
*
* @param ifaces An array of ClassDoc objects
*/
public void processInterfaces(Type[] ifaces) {
if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length);
for (int i = 0; i < ifaces.length; i++) {
String ifaceName = buildEmittableTypeString(ifaces[i]);
if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName);
outputFile.println(" <implements name=\"" + ifaceName + "\"/>");
}//for
}//processInterfaces()
/**
* Process the constructors in the class.
*
* @param ct An array of ConstructorDoc objects
*/
public void processConstructors(ConstructorDoc[] ct) {
if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length);
for (int i = 0; i < ct.length; i++) {
String ctorName = ct[i].name();
if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName);
// Only save the shown elements
if (!shownElement(ct[i], memberVisibilityLevel))
continue;
outputFile.print(" <constructor name=\"" + ctorName + "\"");
Parameter[] params = ct[i].parameters();
boolean first = true;
if (params.length != 0) {
outputFile.print(" type=\"");
for (int j = 0; j < params.length; j++) {
if (!first)
outputFile.print(", ");
emitType(params[j].type());
first = false;
}
outputFile.println("\"");
} else
outputFile.println();
addCommonModifiers(ct[i], 6);
outputFile.println(">");
// Generate the exception elements if any exceptions are thrown
processExceptions(ct[i].thrownExceptions());
addDocumentation(ct[i], 6);
outputFile.println(" </constructor>");
}//for
}//processConstructors()
/**
* Process all exceptions thrown by a constructor or method.
*
* @param cd An array of ClassDoc objects
*/
public void processExceptions(ClassDoc[] cd) {
if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length);
for (int i = 0; i < cd.length; i++) {
String exceptionName = cd[i].name();
if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName);
outputFile.print(" <exception name=\"" + exceptionName + "\" type=\"");
emitType(cd[i]);
outputFile.println("\"/>");
}//for
}//processExceptions()
/**
* Process the methods in the class.
*
* @param md An array of MethodDoc objects
*/
public void processMethods(ClassDoc cd, MethodDoc[] md) {
if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length);
for (int i = 0; i < md.length; i++) {
String methodName = md[i].name();
if (trace) System.out.println("PROCESSING METHOD: " + methodName);
// Skip <init> and <clinit>
if (methodName.startsWith("<"))
continue;
// Only save the shown elements
if (!shownElement(md[i], memberVisibilityLevel))
continue;
outputFile.print(" <method name=\"" + methodName + "\"");
com.sun.javadoc.Type retType = md[i].returnType();
if (retType.qualifiedTypeName().compareTo("void") == 0) {
// Don't add a return attribute if the return type is void
outputFile.println();
} else {
outputFile.print(" return=\"");
emitType(retType);
outputFile.println("\"");
}
outputFile.print(" abstract=\"" + md[i].isAbstract() + "\"");
outputFile.print(" native=\"" + md[i].isNative() + "\"");
outputFile.println(" synchronized=\"" + md[i].isSynchronized() + "\"");
addCommonModifiers(md[i], 6);
outputFile.println(">");
// Generate the parameter elements, if any
Parameter[] params = md[i].parameters();
for (int j = 0; j < params.length; j++) {
outputFile.print(" <param name=\"" + params[j].name() + "\"");
outputFile.print(" type=\"");
emitType(params[j].type());
outputFile.println("\"/>");
}
// Generate the exception elements if any exceptions are thrown
processExceptions(md[i].thrownExceptions());
addDocumentation(md[i], 6);
outputFile.println(" </method>");
}//for
}//processMethods()
/**
* Process the fields in the class.
*
* @param fd An array of FieldDoc objects
*/
public void processFields(FieldDoc[] fd) {
if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length);
for (int i = 0; i < fd.length; i++) {
String fieldName = fd[i].name();
if (trace) System.out.println("PROCESSING FIELD: " + fieldName);
// Only save the shown elements
if (!shownElement(fd[i], memberVisibilityLevel))
continue;
outputFile.print(" <field name=\"" + fieldName + "\"");
outputFile.print(" type=\"");
emitType(fd[i].type());
outputFile.println("\"");
outputFile.print(" transient=\"" + fd[i].isTransient() + "\"");
outputFile.println(" volatile=\"" + fd[i].isVolatile() + "\"");
/* JDK 1.4 and later */
/*
String value = fd[i].constantValueExpression();
if (value != null)
outputFile.println(" value=\"" + value + "\"");
*/
addCommonModifiers(fd[i], 6);
outputFile.println(">");
addDocumentation(fd[i], 6);
outputFile.println(" </field>");
}//for
}//processFields()
/**
* Emit the type name. Removed any prefixed warnings about ambiguity.
* The type maybe an array.
*
* @param type A Type object.
*/
public void emitType(com.sun.javadoc.Type type) {
String name = buildEmittableTypeString(type);
if (name == null)
return;
outputFile.print(name);
}
/**
* Build the emittable type name. The type may be an array and/or
* a generic type.
*
* @param type A Type object
* @return The emittable type name
*/
private String buildEmittableTypeString(com.sun.javadoc.Type type) {
if (type == null) {
return null;
}
// type.toString() returns the fully qualified name of the type
// including the dimension and the parameters we just need to
// escape the generic parameters brackets so that the XML
// generated is correct
String name = type.toString().replaceAll("<", "&lt;").replaceAll(">", "&gt;");
if (name.startsWith("<<ambiguous>>")) {
name = name.substring(13);
}
return name;
}
/**
* Emit the XML header.
*/
public void emitXMLHeader() {
outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
outputFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
outputFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
outputFile.println("<!-- on " + new Date() + " -->");
outputFile.println();
/* No need for this any longer, since doc block text is in an CDATA element
outputFile.println("<!-- XML Schema is used, but XHTML transitional DTD is needed for nbsp -->");
outputFile.println("<!-- entity definitions etc.-->");
outputFile.println("<!DOCTYPE api");
outputFile.println(" PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"");
outputFile.println(" \"" + baseURI + "/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
*/
outputFile.println("<api");
outputFile.println(" xmlns:xsi='" + baseURI + "/2001/XMLSchema-instance'");
outputFile.println(" xsi:noNamespaceSchemaLocation='api.xsd'");
outputFile.println(" name=\"" + apiIdentifier + "\"");
outputFile.println(" jdversion=\"" + JDiff.version + "\">");
outputFile.println();
}
/**
* Emit the XML footer.
*/
public void emitXMLFooter() {
outputFile.println();
outputFile.println("</api>");
}
/**
* Determine if the program element is shown, according to the given
* level of visibility.
*
* @param ped The given program element.
* @param visLevel The desired visibility level; "public", "protected",
* "package" or "private". If null, only check for an exclude tag.
* @return boolean Set if this element is shown.
*/
public boolean shownElement(Doc doc, String visLevel) {
// If a doc block contains @exclude or a similar such tag,
// then don't display it.
if (doExclude && excludeTag != null && doc != null) {
String rct = doc.getRawCommentText();
if (rct != null && rct.indexOf(excludeTag) != -1) {
return false;
}
}
if (visLevel == null) {
return true;
}
ProgramElementDoc ped = null;
if (doc instanceof ProgramElementDoc) {
ped = (ProgramElementDoc)doc;
}
if (visLevel.compareTo("private") == 0)
return true;
// Show all that is not private
if (visLevel.compareTo("package") == 0)
return !ped.isPrivate();
// Show all that is not private or package
if (visLevel.compareTo("protected") == 0)
return !(ped.isPrivate() || ped.isPackagePrivate());
// Show all that is not private or package or protected,
// i.e. all that is public
if (visLevel.compareTo("public") == 0)
return ped.isPublic();
return false;
} //shownElement()
/**
* Strip out non-printing characters, replacing them with a character
* which will not change where the end of the first sentence is found.
* This character is the hash mark, '&#035;'.
*/
public String stripNonPrintingChars(String s, Doc doc) {
if (!stripNonPrintables)
return s;
char[] sa = s.toCharArray();
for (int i = 0; i < sa.length; i++) {
char c = sa[i];
// TODO still have an issue with Unicode: 0xfc in java.lang.String.toUpperCase comments
// if (Character.isDefined(c))
if (Character.isLetterOrDigit(c))
continue;
// There must be a better way that is still platform independent!
if (c == ' ' ||
c == '.' ||
c == ',' ||
c == '\r' ||
c == '\t' ||
c == '\n' ||
c == '!' ||
c == '?' ||
c == ';' ||
c == ':' ||
c == '[' ||
c == ']' ||
c == '(' ||
c == ')' ||
c == '~' ||
c == '@' ||
c == '#' ||
c == '$' ||
c == '%' ||
c == '^' ||
c == '&' ||
c == '*' ||
c == '-' ||
c == '=' ||
c == '+' ||
c == '_' ||
c == '|' ||
c == '\\' ||
c == '/' ||
c == '\'' ||
c == '}' ||
c == '{' ||
c == '"' ||
c == '<' ||
c == '>' ||
c == '`'
)
continue;
/* Doesn't seem to return the expected values?
int val = Character.getNumericValue(c);
// if (s.indexOf("which is also a test for non-printable") != -1)
// System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG
// Ranges from http://www.unicode.org/unicode/reports/tr20/
// Should really replace 0x2028 and 0x2029 with <br/>
if (val == 0x0 ||
inRange(val, 0x2028, 0x2029) ||
inRange(val, 0x202A, 0x202E) ||
inRange(val, 0x206A, 0x206F) ||
inRange(val, 0xFFF9, 0xFFFC) ||
inRange(val, 0xE0000, 0xE007F)) {
if (trace) {
System.out.println("Warning: changed non-printing character " + sa[i] + " in " + doc.name());
}
sa[i] = '#';
}
*/
// Replace the non-printable character with a printable character
// which does not change the end of the first sentence
sa[i] = '#';
}
return new String(sa);
}
/** Return true if val is in the range [min|max], inclusive. */
public boolean inRange(int val, int min, int max) {
if (val < min)
return false;
if (val > max)
return false;
return true;
}
/**
* Add at least the first sentence from a doc block to the API. This is
* used by the report generator if no comment is provided.
* Need to make sure that HTML tags are not confused with XML tags.
* This could be done by stuffing the &lt; character to another string
* or by handling HTML in the parser. This second option seems neater. Note that
* XML expects all element tags to have either a closing "/>" or a matching
* end element tag. Due to the difficulties of converting incorrect HTML
* to XHTML, the first option is used.
*/
public void addDocumentation(ProgramElementDoc ped, int indent) {
String rct = ((Doc)ped).getRawCommentText();
if (rct != null) {
rct = stripNonPrintingChars(rct, (Doc)ped);
rct = rct.trim();
if (rct.compareTo("") != 0 &&
rct.indexOf(Comments.placeHolderText) == -1 &&
rct.indexOf("InsertOtherCommentsHere") == -1) {
int idx = endOfFirstSentence(rct);
if (idx == 0)
return;
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.println("<doc>");
for (int i = 0; i < indent; i++) outputFile.print(" ");
String firstSentence = null;
if (idx == -1)
firstSentence = rct;
else
firstSentence = rct.substring(0, idx+1);
boolean checkForAts = false;
if (checkForAts && firstSentence.indexOf("@") != -1 &&
firstSentence.indexOf("@link") == -1) {
System.out.println("Warning: @ tag seen in comment: " +
firstSentence);
}
String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
outputFile.println(firstSentenceNoTags);
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.println("</doc>");
}
}
}
/**
* Add at least the first sentence from a doc block for a package to the API. This is
* used by the report generator if no comment is provided.
* The default source tree may not include the package.html files, so
* this may be unavailable in many cases.
* Need to make sure that HTML tags are not confused with XML tags.
* This could be done by stuffing the &lt; character to another string
* or by handling HTML in the parser. This second option is neater. Note that
* XML expects all element tags to have either a closing "/>" or a matching
* end element tag. Due to the difficulties of converting incorrect HTML
* to XHTML, the first option is used.
*/
public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) {
String rct = null;
String filename = pd.name();
try {
// See if the source path was specified as part of the
// options and prepend it if it was.
String srcLocation = null;
String[][] options = root.options();
for (int opt = 0; opt < options.length; opt++) {
if ((options[opt][0]).compareTo("-sourcepath") == 0) {
srcLocation = options[opt][1];
break;
}
}
filename = filename.replace('.', JDiff.DIR_SEP.charAt(0));
if (srcLocation != null) {
// Make a relative location absolute
if (srcLocation.startsWith("..")) {
String curDir = System.getProperty("user.dir");
while (srcLocation.startsWith("..")) {
srcLocation = srcLocation.substring(3);
int idx = curDir.lastIndexOf(JDiff.DIR_SEP);
curDir = curDir.substring(0, idx+1);
}
srcLocation = curDir + srcLocation;
}
filename = srcLocation + JDiff.DIR_SEP + filename;
}
// Try both ".htm" and ".html"
filename += JDiff.DIR_SEP + "package.htm";
File f2 = new File(filename);
if (!f2.exists()) {
filename += "l";
}
FileInputStream f = new FileInputStream(filename);
BufferedReader d = new BufferedReader(new InputStreamReader(f));
String str = d.readLine();
// Ignore everything except the lines between <body> elements
boolean inBody = false;
while(str != null) {
if (!inBody) {
if (str.toLowerCase().trim().startsWith("<body")) {
inBody = true;
}
str = d.readLine(); // Get the next line
continue; // Ignore the line
} else {
if (str.toLowerCase().trim().startsWith("</body")) {
inBody = false;
continue; // Ignore the line
}
}
if (rct == null)
rct = str + "\n";
else
rct += str + "\n";
str = d.readLine();
}
} catch(java.io.FileNotFoundException e) {
// If it doesn't exist, that's fine
if (trace)
System.out.println("No package level documentation file at '" + filename + "'");
} catch(java.io.IOException e) {
System.out.println("Error reading file \"" + filename + "\": " + e.getMessage());
System.exit(5);
}
if (rct != null) {
rct = stripNonPrintingChars(rct, (Doc)pd);
rct = rct.trim();
if (rct.compareTo("") != 0 &&
rct.indexOf(Comments.placeHolderText) == -1 &&
rct.indexOf("InsertOtherCommentsHere") == -1) {
int idx = endOfFirstSentence(rct);
if (idx == 0)
return;
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.println("<doc>");
for (int i = 0; i < indent; i++) outputFile.print(" ");
String firstSentence = null;
if (idx == -1)
firstSentence = rct;
else
firstSentence = rct.substring(0, idx+1);
String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
outputFile.println(firstSentenceNoTags);
for (int i = 0; i < indent; i++) outputFile.print(" ");
outputFile.println("</doc>");
}
}
}
/**
* Find the index of the end of the first sentence in the given text,
* when writing out to an XML file.
* This is an extended version of the algorithm used by the DocCheck
* Javadoc doclet. It checks for @tags too.
*
* @param text The text to be searched.
* @return The index of the end of the first sentence. If there is no
* end, return -1. If there is no useful text, return 0.
* If the whole doc block comment is wanted (default), return -1.
*/
public static int endOfFirstSentence(String text) {
return endOfFirstSentence(text, true);
}
/**
* Find the index of the end of the first sentence in the given text.
* This is an extended version of the algorithm used by the DocCheck
* Javadoc doclet. It checks for &#064;tags too.
*
* @param text The text to be searched.
* @param writingToXML Set to true when writing out XML.
* @return The index of the end of the first sentence. If there is no
* end, return -1. If there is no useful text, return 0.
* If the whole doc block comment is wanted (default), return -1.
*/
public static int endOfFirstSentence(String text, boolean writingToXML) {
if (saveAllDocs && writingToXML)
return -1;
int textLen = text.length();
if (textLen == 0)
return 0;
int index = -1;
// Handle some special cases
int fromindex = 0;
int ellipsis = text.indexOf(". . ."); // Handles one instance of this
if (ellipsis != -1)
fromindex = ellipsis + 5;
// If the first non-whitespace character is an @, go beyond it
int i = 0;
while (i < textLen && text.charAt(i) == ' ') {
i++;
}
if (text.charAt(i) == '@' && fromindex < textLen-1)
fromindex = i + 1;
// Use the brute force approach.
index = minIndex(index, text.indexOf("? ", fromindex));
index = minIndex(index, text.indexOf("?\t", fromindex));
index = minIndex(index, text.indexOf("?\n", fromindex));
index = minIndex(index, text.indexOf("?\r", fromindex));
index = minIndex(index, text.indexOf("?\f", fromindex));
index = minIndex(index, text.indexOf("! ", fromindex));
index = minIndex(index, text.indexOf("!\t", fromindex));
index = minIndex(index, text.indexOf("!\n", fromindex));
index = minIndex(index, text.indexOf("!\r", fromindex));
index = minIndex(index, text.indexOf("!\f", fromindex));
index = minIndex(index, text.indexOf(". ", fromindex));
index = minIndex(index, text.indexOf(".\t", fromindex));
index = minIndex(index, text.indexOf(".\n", fromindex));
index = minIndex(index, text.indexOf(".\r", fromindex));
index = minIndex(index, text.indexOf(".\f", fromindex));
index = minIndex(index, text.indexOf("@param", fromindex));
index = minIndex(index, text.indexOf("@return", fromindex));
index = minIndex(index, text.indexOf("@throw", fromindex));
index = minIndex(index, text.indexOf("@serial", fromindex));
index = minIndex(index, text.indexOf("@exception", fromindex));
index = minIndex(index, text.indexOf("@deprecate", fromindex));
index = minIndex(index, text.indexOf("@author", fromindex));
index = minIndex(index, text.indexOf("@since", fromindex));
index = minIndex(index, text.indexOf("@see", fromindex));
index = minIndex(index, text.indexOf("@version", fromindex));
if (doExclude && excludeTag != null)
index = minIndex(index, text.indexOf(excludeTag));
index = minIndex(index, text.indexOf("@vtexclude", fromindex));
index = minIndex(index, text.indexOf("@vtinclude", fromindex));
index = minIndex(index, text.indexOf("<p>", 2)); // Not at start
index = minIndex(index, text.indexOf("<P>", 2)); // Not at start
index = minIndex(index, text.indexOf("<blockquote", 2)); // Not at start
index = minIndex(index, text.indexOf("<pre", fromindex)); // May contain anything!
// Avoid the char at the start of a tag in some cases
if (index != -1 &&
(text.charAt(index) == '@' || text.charAt(index) == '<')) {
if (index != 0)
index--;
}
/* Not used for jdiff, since tags are explicitly checked for above.
// Look for a sentence terminated by an HTML tag.
index = minIndex(index, text.indexOf(".<", fromindex));
if (index == -1) {
// If period-whitespace etc was not found, check to see if
// last character is a period,
int endIndex = text.length()-1;
if (text.charAt(endIndex) == '.' ||
text.charAt(endIndex) == '?' ||
text.charAt(endIndex) == '!')
index = endIndex;
}
*/
return index;
}
/**
* Return the minimum of two indexes if > -1, and return -1
* only if both indexes = -1.
* @param i an int index
* @param j an int index
* @return an int equal to the minimum index > -1, or -1
*/
public static int minIndex(int i, int j) {
if (i == -1) return j;
if (j == -1) return i;
return Math.min(i,j);
}
/**
* The name of the file where the XML representing the API will be
* stored.
*/
public static String outputFileName = null;
/**
* The identifier of the API being written out in XML, e.g.
* &quotSuperProduct 1.3&quot;.
*/
public static String apiIdentifier = null;
/**
* The file where the XML representing the API will be stored.
*/
private static PrintWriter outputFile = null;
/**
* The name of the directory where the XML representing the API will be
* stored.
*/
public static String outputDirectory = null;
/**
* Do not display a class with a lower level of visibility than this.
* Default is to display all public and protected classes.
*/
public static String classVisibilityLevel = "protected";
/**
* Do not display a member with a lower level of visibility than this.
* Default is to display all public and protected members
* (constructors, methods, fields).
*/
public static String memberVisibilityLevel = "protected";
/**
* If set, then save the entire contents of a doc block comment in the
* API file. If not set, then just save the first sentence. Default is
* that this is set.
*/
public static boolean saveAllDocs = true;
/**
* If set, exclude program elements marked with whatever the exclude tag
* is specified as, e.g. "@exclude".
*/
public static boolean doExclude = false;
/**
* Exclude program elements marked with this String, e.g. "@exclude".
*/
public static String excludeTag = null;
/**
* The base URI for locating necessary DTDs and Schemas. By default, this
* is "http://www.w3.org". A typical value to use local copies of DTD files
* might be "file:///C:/jdiff/lib"
*/
public static String baseURI = "http://www.w3.org";
/**
* If set, then strip out non-printing characters from documentation.
* Default is that this is set.
*/
static boolean stripNonPrintables = true;
/**
* If set, then add the information about the source file and line number
* which is available in J2SE1.4. Default is that this is not set.
*/
static boolean addSrcInfo = false;
/**
* If set, scan classes with no packages.
* If the source is a jar file this may duplicates classes, so
* disable it using the -packagesonly option. Default is that this is
* not set.
*/
static boolean packagesOnly = false;
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
} //RootDocToXML