diff options
Diffstat (limited to 'src/jdiff/RootDocToXML.java')
-rwxr-xr-x | src/jdiff/RootDocToXML.java | 1147 |
1 files changed, 1147 insertions, 0 deletions
diff --git a/src/jdiff/RootDocToXML.java b/src/jdiff/RootDocToXML.java new file mode 100755 index 0000000..f68a60b --- /dev/null +++ b/src/jdiff/RootDocToXML.java @@ -0,0 +1,1147 @@ +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("<", "<").replaceAll(">", ">"); + 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, '#'. + */ + 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 < 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 < 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 @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. + * "SuperProduct 1.3". + */ + 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 |