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", null); Object sp = m.invoke(ped, 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("<", "<"). 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