Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • irt/autopsy
1 result
Show changes
Showing
with 0 additions and 7833 deletions
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent a field, analogous to FieldDoc in the
* Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this field.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class FieldAPI implements Comparable {
/** Name of the field. */
public String name_;
/** Type of the field. */
public String type_;
/**
* The fully qualified name of the class or interface this field is
* inherited from. If this is null, then the field is defined locally
* in this class or interface.
*/
public String inheritedFrom_ = null;
/** Set if this field is transient. */
public boolean isTransient_ = false;
/** Set if this field is volatile. */
public boolean isVolatile_ = false;
/** If non-null, this is the value of this field. */
public String value_ = null;
/** Modifiers for this class. */
public Modifiers modifiers_;
/** The doc block, default is null. */
public String doc_ = null;
/** Constructor. */
public FieldAPI(String name, String type,
boolean isTransient, boolean isVolatile,
String value, Modifiers modifiers) {
name_ = name;
type_ = type;
isTransient_ = isTransient;
isVolatile_ = isVolatile;
value_ = value;
modifiers_ = modifiers;
}
/** Copy constructor. */
public FieldAPI(FieldAPI f) {
name_ = f.name_;
type_ = f.type_;
inheritedFrom_ = f.inheritedFrom_;
isTransient_ = f.isTransient_;
isVolatile_ = f.isVolatile_;
value_ = f.value_;
modifiers_ = f.modifiers_; // Note: shallow copy
doc_ = f.doc_;
}
/** Compare two FieldAPI objects, including name, type and modifiers. */
public int compareTo(Object o) {
FieldAPI oFieldAPI = (FieldAPI)o;
int comp = name_.compareTo(oFieldAPI.name_);
if (comp != 0)
return comp;
comp = type_.compareTo(oFieldAPI.type_);
if (comp != 0)
return comp;
if (APIComparator.changedInheritance(inheritedFrom_, oFieldAPI.inheritedFrom_) != 0)
return -1;
if (isTransient_ != oFieldAPI.isTransient_) {
return -1;
}
if (isVolatile_ != oFieldAPI.isVolatile_) {
return -1;
}
if (value_ != null && oFieldAPI.value_ != null) {
comp = value_.compareTo(oFieldAPI.value_);
if (comp != 0)
return comp;
}
comp = modifiers_.compareTo(oFieldAPI.modifiers_);
if (comp != 0)
return comp;
if (APIComparator.docChanged(doc_, oFieldAPI.doc_))
return -1;
return 0;
}
/**
* Tests two fields, using just the field name, used by indexOf().
*/
public boolean equals(Object o) {
if (name_.compareTo(((FieldAPI)o).name_) == 0)
return true;
return false;
}
}
package jdiff;
import java.util.*;
import java.io.*;
/**
* Emit HTML files for the supporting infrastructure for the HTML report.
* Examples are stylesheets, help files, frame files.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class HTMLFiles {
/** Constructor. */
public HTMLFiles(HTMLReportGenerator h) {
h_ = h;
}
/** The HTMLReportGenerator instance used to write HTML. */
private HTMLReportGenerator h_ = null;
/**
* Emit the top-level changes.html frames file where everything starts.
*/
public void emitTopLevelFile(String tln,
APIDiff apiDiff) {
try {
FileOutputStream fos = new FileOutputStream(tln);
h_.reportFile = new PrintWriter(fos);
// Write out the HTML header
h_.writeStartHTMLHeaderWithDate();
// Write out the title
String oldAPIName = "Old API";
if (apiDiff.oldAPIName_ != null)
oldAPIName = apiDiff.oldAPIName_;
String newAPIName = "New API";
if (apiDiff.newAPIName_ != null)
newAPIName = apiDiff.newAPIName_;
if (h_.windowTitle == null)
h_.writeHTMLTitle("API Differences between " + oldAPIName + " and " + newAPIName);
else
h_.writeHTMLTitle(h_.windowTitle);
// Note that the stylesheet is in the same directory
h_.writeStyleSheetRef(true);
h_.writeText("</HEAD>");
// Note that the top-level frame file doesn't have the BODY tag
h_.writeText("<FRAMESET COLS=\"20%,80%\">");
h_.writeText(" <FRAMESET ROWS=\"25%,75%\">");
// Convert filenames to web links
String tlfLink = h_.reportFileName + "/jdiff_topleftframe" + h_.reportFileExt;
String allDiffsLink = h_.reportFileName + "/alldiffs_index_all" + h_.reportFileExt;
String csnLink = h_.reportFileName + "/" + h_.reportFileName + "-summary" + h_.reportFileExt;
h_.writeText(" <FRAME SRC=\"" + tlfLink + "\" SCROLLING=\"no\" NAME=\"topleftframe\">");
h_.writeText(" <FRAME SRC=\"" + allDiffsLink + "\" SCROLLING=\"auto\" NAME=\"bottomleftframe\">");
h_.writeText(" </FRAMESET>");
h_.writeText(" <FRAME SRC=\"" + csnLink + "\" SCROLLING=\"auto\" NAME=\"rightframe\">");
h_.writeText("</FRAMESET>");
h_.writeText("<NOFRAMES>");
h_.writeText("<H2>");
h_.writeText("Frame Alert");
h_.writeText("</H2>\n");
h_.writeText("<P>");
h_.writeText("This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client.");
h_.writeText("<BR>");
h_.writeText("Link to <A HREF=\"" + csnLink + "\" target=\"_top\">Non-frame version.</A>");
h_.writeText("</NOFRAMES>");
h_.writeText("</HTML>");
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + tln);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/** Emit a top left frame with all the links to the index files. */
public void emitTopLeftFile(String tlf) {
try {
FileOutputStream fos = new FileOutputStream(tlf);
h_.reportFile = new PrintWriter(fos);
h_.writeStartHTMLHeader();
h_.writeHTMLTitle("JDiff");
h_.writeStyleSheetRef();
h_.writeText("</HEAD>");
h_.writeText("<BODY>");
h_.writeText("<TABLE summary=\"Links to all index files\" BORDER=\"0\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFCC\"><FONT size=\"+1\">");
h_.writeText(" <B>JDiff&nbsp;Indexes</B></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"alldiffs_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">All Differences</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"packages_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Package</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"classes_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Class</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"constructors_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Constructor</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"methods_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Method</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><A HREF=\"fields_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Field</A></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("</TABLE>");
h_.writeHTMLFooter();
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + tlf);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/** Emit the help file. */
public void emitHelp(String fullReportFileName, APIDiff apiDiff) {
String helpFileName = fullReportFileName + JDiff.DIR_SEP + "jdiff_help" + h_.reportFileExt;
try {
FileOutputStream fos = new FileOutputStream(helpFileName);
h_.reportFile = new PrintWriter(fos);
h_.writeStartHTMLHeader();
h_.writeHTMLTitle("JDiff Help");
h_.writeStyleSheetRef();
h_.writeText("</HEAD>");
h_.writeText("<BODY>");
// Write a customized navigation bar for the help page
h_.writeText("<!-- Start of nav bar -->");
h_.writeText("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
h_.writeText("<TR>");
h_.writeText("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
h_.writeText(" <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
h_.writeText(" <TR ALIGN=\"center\" VALIGN=\"top\">");
// Always have a link to the Javadoc files
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
if (!Diff.noDocDiffs) {
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
}
if (h_.doStats) {
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
}
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Help</B></FONT>&nbsp;</TD>");
h_.writeText(" </TR>");
h_.writeText(" </TABLE>");
h_.writeText("</TD>");
// The right hand side title
h_.writeText("<TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3><EM><b>Generated by<br><a href=\"" + JDiff.jDiffLocation + "\" class=\"staysblack\" target=\"_top\">JDiff</a></b></EM></TD>");
h_.writeText("</TR>");
// Links for frames and no frames
h_.writeText("<TR>");
h_.writeText(" <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\"></FONT>");
h_.writeText("</TD>");
h_.writeText(" <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
h_.writeText(" <A HREF=\"" + "../" + h_.reportFileName + h_.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A> &nbsp;");
h_.writeText(" &nbsp;<A HREF=\"jdiff_help" + h_.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
h_.writeText("</TR>");
h_.writeText("</TABLE>");
h_.writeText("<HR>");
h_.writeText ("<!-- End of nav bar -->");
h_.writeText("<center>");
h_.writeText("<H1>JDiff Documentation</H1>");
h_.writeText("</center>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("JDiff is a <a href=\"http://java.sun.com/j2se/javadoc/\" target=\"_top\">Javadoc</a> doclet which generates a report of the API differences between two versions of a product. It does not report changes in Javadoc comments, or changes in what a class or method does. ");
h_.writeText("This help page describes the different parts of the output from JDiff.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText(" See the reference page in the <a href=\"" + JDiff.jDiffLocation + "\">source for JDiff</a> for information about how to generate a report like this one.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("The indexes shown in the top-left frame help show each type of change in more detail. The index \"All Differences\" contains all the differences between the APIs, in alphabetical order. ");
h_.writeText("These indexes all use the same format:");
h_.writeText("<ul>");
h_.writeText("<li>Removed packages, classes, constructors, methods and fields are <strike>struck through</strike>.</li>");
h_.writeText("<li>Added packages, classes, constructors, methods and fields appear in <b>bold</b>.</li>");
h_.writeText("<li>Changed packages, classes, constructors, methods and fields appear in normal text.</li>");
h_.writeText("</ul>");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("You can always tell when you are reading a JDiff page, rather than a Javadoc page, by the color of the index bar and the color of the background. ");
h_.writeText("Links which take you to a Javadoc page are always in a <tt>typewriter</tt> font. ");
h_.writeText("Just like Javadoc, all interface names are in <i>italic</i>, and class names are not italicized. Where there are multiple entries in an index with the same name, the heading for them is also in italics, but is not a link.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3><b><tt>Javadoc</tt></b></H3>");
h_.writeText("This is a link to the <a href=\"" + h_.newDocPrefix + "index.html\" target=\"_top\">top-level</a> Javadoc page for the new version of the product.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Overview</H3>");
h_.writeText("The <a href=\"" + h_.reportFileName + "-summary" +
h_.reportFileExt + "\">overview</a> is the top-level summary of what was removed, added and changed between versions.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Package</H3>");
h_.writeText("This is a link to the package containing the current changed class or interface.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Class</H3>");
h_.writeText("This is highlighted when you are looking at the changed class or interface.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Text Changes</H3>");
h_.writeText("This is a link to the top-level index of all documentation changes for the current package or class. ");
h_.writeText("If it is not present, then there are no documentation changes for the current package or class. ");
h_.writeText("This link can be removed entirely by not using the <code>-docchanges</code> option.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Statistics</H3>");
h_.writeText("This is a link to a page which shows statistics about the changes between the two APIs.");
h_.writeText("This link can be removed entirely by not using the <code>-stats</code> option.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Help</H3>");
h_.writeText("A link to this Help page for JDiff.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Prev/Next</H3>");
h_.writeText("These links take you to the previous and next changed package or class.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H3>Frames/No Frames</H3>");
h_.writeText("These links show and hide the HTML frames. All pages are available with or without frames.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("<H2>Complex Changes</H2>");
h_.writeText("There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass. ");
h_.writeText("In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes. ");
h_.writeText("</BLOCKQUOTE>");
h_.writeHTMLFooter();
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + helpFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/** Emit the CSS external stylesheet file. */
public void emitStylesheet() {
String stylesheetFileName = "stylesheet-jdiff.css";
if (h_.outputDir != null)
stylesheetFileName = h_.outputDir + JDiff.DIR_SEP +stylesheetFileName;
try {
FileOutputStream fos = new FileOutputStream(stylesheetFileName);
h_.reportFile = new PrintWriter(fos);
h_.writeText();
h_.writeText("/* The JDiff style sheet, derived from the Javadoc style sheet. */");
h_.writeText("/* Generated by the JDiff Javadoc doclet */");
h_.writeText("/* (" + JDiff.jDiffLocation + ") */");
// h_.writeText("/* on " + new Date() + " */");
h_.writeText();
h_.writeText("/* Define colors, fonts and other style attributes here to override the defaults */");
h_.writeText();
h_.writeText("/* Page background color */");
// h_.writeText("body { background-color: " + h_.bgcolor + "; font-family: arial; }");
// First argument after backgroun: is for older Netscape browsers
// For more information, see http://css.nu/pointers/bugs.html and
// http://www.richinstyle.com/bugs/netscape4.html
h_.writeText("body { background: #CCFFFF url(background.gif); font-family: arial; }");
h_.writeText();
h_.writeText("/* Table colors */");
h_.writeText(".TableHeadingColor { background: #CCCCFF } /* Dark mauve */");
h_.writeText(".TableSubHeadingColor { background: #EEEEFF } /* Light mauve */");
h_.writeText(".TableRowColor { background: #FFFFFF } /* White */");
h_.writeText();
h_.writeText("/* Font used in left-hand frame lists */");
h_.writeText(".FrameTitleFont { font-size: normal; font-family: normal }");
h_.writeText(".FrameHeadingFont { font-size: normal; font-family: normal }");
h_.writeText(".FrameItemFont { font-size: normal; font-family: normal }");
h_.writeText();
h_.writeText("/* Example of smaller, sans-serif font in frames */");
h_.writeText("/* .FrameItemFont { font-size: 10pt; font-family: Helvetica, Arial, sans-serif } */");
h_.writeText();
h_.writeText("/* Navigation bar fonts and colors */");
h_.writeText(".NavBarCell1 { background-color:#FFFFCC;} /* Changed to yellowish to make difference from Javadoc clear */");
h_.writeText(".NavBarCell1Rev { background-color:#00008B;}/* Dark Blue */");
h_.writeText(".NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;}");
h_.writeText(".NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;}");
h_.writeText();
h_.writeText(".NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;}");
h_.writeText(".NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;}");
h_.writeText();
h_.writeText("/* ");
h_.writeText(" Links which become blue when hovered upon and show that they have been ");
h_.writeText(" visited. ");
h_.writeText("*/");
h_.writeText("a.hiddenlink:link {color: black; text-decoration: none}");
h_.writeText("a.hiddenlink:visited {color: purple; text-decoration: none}");
h_.writeText("a.hiddenlink:hover {color: blue; text-decoration: underline;}");
h_.writeText();
h_.writeText("/* ");
h_.writeText(" Links which become blue when hovered upon but do not show that they have ");
h_.writeText(" been visited. ");
h_.writeText("*/");
h_.writeText("a.staysblack:link {color: black; text-decoration: none}");
h_.writeText("a.staysblack:visited {color: black; text-decoration: none}");
h_.writeText("a.staysblack:hover {color: blue; text-decoration: underline;}");
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + stylesheetFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
}
package jdiff;
import java.util.*;
import java.io.*;
/**
* Emit HTML indexes which appear in the bottom left frame in the report.
* All indexes are links to JDiff-generated pages.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class HTMLIndexes {
/** Constructor. */
public HTMLIndexes(HTMLReportGenerator h) {
h_ = h;
}
/** The HTMLReportGenerator instance used to write HTML. */
private HTMLReportGenerator h_ = null;
/** Emit all the bottom left frame index files. */
public void emitAllBottomLeftFiles(String packagesIndexName,
String classesIndexName,
String constructorsIndexName,
String methodsIndexName,
String fieldsIndexName,
String allDiffsIndexName,
APIDiff apiDiff) {
// indexType values: 0 = removals only, 1 = additions only,
// 2 = changes only. 3 = all differences. Run all differences
// first for all program element types so we know whether there
// are any removals etc for the allDiffs index.
emitBottomLeftFile(packagesIndexName, apiDiff, 3, "Package");
emitBottomLeftFile(classesIndexName, apiDiff, 3, "Class");
emitBottomLeftFile(constructorsIndexName, apiDiff, 3, "Constructor");
emitBottomLeftFile(methodsIndexName, apiDiff, 3, "Method");
emitBottomLeftFile(fieldsIndexName, apiDiff, 3, "Field");
// The allindex must be done last, since it uses the results from
// the previous ones
emitBottomLeftFile(allDiffsIndexName, apiDiff, 3, "All");
// Now generate the other indexes
for (int indexType = 0; indexType < 3; indexType++) {
emitBottomLeftFile(packagesIndexName, apiDiff, indexType, "Package");
emitBottomLeftFile(classesIndexName, apiDiff, indexType, "Class");
emitBottomLeftFile(constructorsIndexName, apiDiff, indexType, "Constructor");
emitBottomLeftFile(methodsIndexName, apiDiff, indexType, "Method");
emitBottomLeftFile(fieldsIndexName, apiDiff, indexType, "Field");
emitBottomLeftFile(allDiffsIndexName, apiDiff, indexType, "All");
}
if (missingSincesFile != null)
missingSincesFile.close();
}
/**
* Emit a single bottom left frame with the given kind of differences for
* the given program element type in an alphabetical index.
*
* @param indexBaseName The base name of the index file.
* @param apiDiff The root element containing all the API differences.
* @param indexType 0 = removals only, 1 = additions only,
* 2 = changes only, 3 = all differences,
* @param programElementType "Package", "Class", "Constructor",
* "Method", "Field" or "All".
*/
public void emitBottomLeftFile(String indexBaseName,
APIDiff apiDiff, int indexType,
String programElementType) {
String filename = indexBaseName;
try {
String title = "JDiff";
if (indexType == 0) {
filename += "_removals" + h_.reportFileExt;
title = programElementType + " Removals Index";
} else if (indexType == 1) {
filename += "_additions" + h_.reportFileExt;
title = programElementType + " Additions Index";
} else if (indexType == 2) {
filename += "_changes" + h_.reportFileExt;
title = programElementType + " Changes Index";
} else if (indexType == 3) {
filename += "_all" + h_.reportFileExt;
title = programElementType + " Differences Index";
}
FileOutputStream fos = new FileOutputStream(filename);
h_.reportFile = new PrintWriter(fos);
h_.writeStartHTMLHeader();
h_.writeHTMLTitle(title);
h_.writeStyleSheetRef();
h_.writeText("</HEAD>");
h_.writeText("<BODY>");
if (programElementType.compareTo("Package") == 0) {
emitPackagesIndex(apiDiff, indexType);
} else if (programElementType.compareTo("Class") == 0) {
emitClassesIndex(apiDiff, indexType);
} else if (programElementType.compareTo("Constructor") == 0) {
emitConstructorsIndex(apiDiff, indexType);
} else if (programElementType.compareTo("Method") == 0) {
emitMethodsIndex(apiDiff, indexType);
} else if (programElementType.compareTo("Field") == 0) {
emitFieldsIndex(apiDiff, indexType);
} else if (programElementType.compareTo("All") == 0) {
emitAllDiffsIndex(apiDiff, indexType);
} else{
System.out.println("Error: unknown program element type.");
System.exit(3);
}
h_.writeHTMLFooter();
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + filename);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/**
* Generate a small header of letters which link to each section, but
* do not emit a linked letter for the current section. Finish the list off
* with a link to the top of the index.
* Caching the results of this function would save about 10s with large APIs.
*/
private void generateLetterIndex(List list, char currChar, boolean larger) {
if (larger)
return; // Currently not using the larger functionality
int size = -2;
if (larger)
size = -1;
Iterator iter = null;
if (isAllNames)
iter = allNames.iterator();
else
iter = list.iterator();
char oldsw = '\0';
while (iter.hasNext()) {
Index entry = (Index)(iter.next());
char sw = entry.name_.charAt(0);
char swu = Character.toUpperCase(sw);
if (swu != Character.toUpperCase(oldsw)) {
// Don't emit a reference to the current letter
if (Character.toUpperCase(sw) != Character.toUpperCase(currChar)) {
if (swu == '_') {
h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + "underscore" + "</font></a> ");
} else {
h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + swu + "</font></a> ");
}
}
oldsw = sw;
}
}
h_.writeText(" <a href=\"#topheader\"><font size=\"" + size + "\">TOP</font></a>");
h_.writeText("<br>");
}
/**
* Emit a header for an index, including suitable links for removed,
* added and changes sub-indexes.
*/
private void emitIndexHeader(String indexName, int indexType,
boolean hasRemovals,
boolean hasAdditions, boolean hasChanges) {
String linkIndexName = indexName.toLowerCase();
boolean isAllDiffs = false;
if (indexName.compareTo("All Differences") == 0) {
linkIndexName = "alldiffs";
isAllDiffs = true;
}
h_.writeText("<a NAME=\"topheader\"></a>"); // Named anchor
h_.writeText("<table summary=\"Index for " + indexName + "\" width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText(" <tr>");
h_.writeText(" <td bgcolor=\"#FFFFCC\">");
// The index name is also a hidden link to the *index_all page
if (isAllDiffs)
h_.writeText("<font size=\"+1\"><a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"staysblack\">" + indexName + "</a></font>");
else
h_.writeText("<font size=\"+1\"><a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"staysblack\">All " + indexName + "</a></font>");
h_.writeText(" </td>");
h_.writeText(" </tr>");
h_.writeText(" <tr>");
h_.writeText(" <td bgcolor=\"#FFFFFF\">");
h_.writeText(" <FONT SIZE=\"-1\">");
if (hasRemovals) {
if (indexType == 0) {
h_.writeText("<b>Removals</b>");
} else {
h_.writeText("<A HREF=\"" + linkIndexName + "_index_removals" + h_.reportFileExt + "\" class=\"hiddenlink\">Removals</A>");
}
} else {
h_.writeText("<font color=\"#999999\">Removals</font>");
}
h_.writeText(" </FONT>");
h_.writeText(" </td>");
h_.writeText(" </tr>");
h_.writeText(" <tr>");
h_.writeText(" <td bgcolor=\"#FFFFFF\">");
h_.writeText(" <FONT SIZE=\"-1\">");
if (hasAdditions) {
if (indexType == 1) {
h_.writeText("<b>Additions</b>");
} else {
h_.writeText("<A HREF=\"" + linkIndexName + "_index_additions" + h_.reportFileExt + "\"class=\"hiddenlink\">Additions</A>");
}
} else {
h_.writeText("<font color=\"#999999\">Additions</font>");
}
h_.writeText(" </FONT>");
h_.writeText(" </td>");
h_.writeText(" </tr>");
h_.writeText(" <tr>");
h_.writeText(" <td bgcolor=\"#FFFFFF\">");
h_.writeText(" <FONT SIZE=\"-1\">");
if (hasChanges) {
if (indexType == 2) {
h_.writeText("<b>Changes</b>");
} else {
h_.writeText("<A HREF=\"" + linkIndexName + "_index_changes" + h_.reportFileExt + "\"class=\"hiddenlink\">Changes</A>");
}
} else {
h_.writeText("<font color=\"#999999\">Changes</font>");
}
h_.writeText(" </FONT>");
h_.writeText(" </td>");
h_.writeText(" </tr>");
h_.writeText(" <tr>");
h_.writeText(" <td>");
h_.writeText("<font size=\"-2\"><b>Bold</b>&nbsp;is&nbsp;New,&nbsp;<strike>strike</strike>&nbsp;is&nbsp;deleted</font>");
h_.writeText(" </td>");
h_.writeText(" </tr>");
h_.writeText("</table><br>");
}
/** Emit the index of packages, which appears in the bottom left frame. */
public void emitPackagesIndex(APIDiff apiDiff, int indexType) {
// Add all the names of packages to a new list, to be sorted later
packageNames = new ArrayList(); // Index[]
boolean hasRemovals = false;
if (apiDiff.packagesRemoved.size() != 0)
hasRemovals = true;
boolean hasAdditions = false;
if (apiDiff.packagesAdded.size() != 0)
hasAdditions = true;
boolean hasChanges = false;
if (apiDiff.packagesChanged.size() != 0)
hasChanges = true;
recordDiffs(hasRemovals, hasAdditions, hasChanges);
Iterator iter = apiDiff.packagesRemoved.iterator();
while ((indexType == 3 || indexType == 0) && iter.hasNext()) {
PackageAPI pkg = (PackageAPI)(iter.next());
packageNames.add(new Index(pkg.name_, 0));
}
iter = apiDiff.packagesAdded.iterator();
while ((indexType == 3 || indexType == 1) && iter.hasNext()) {
PackageAPI pkg = (PackageAPI)(iter.next());
packageNames.add(new Index(pkg.name_, 1));
}
iter = apiDiff.packagesChanged.iterator();
while ((indexType == 3 || indexType == 2) && iter.hasNext()) {
PackageDiff pkg = (PackageDiff)(iter.next());
packageNames.add(new Index(pkg.name_, 2));
}
Collections.sort(packageNames);
// No letter index needed for packages
// Now emit all the package names and links to their respective files
emitIndexHeader("Packages", indexType, hasRemovals, hasAdditions, hasChanges);
// Extra line because no index is emitted
h_.writeText("<br>");
// Package names are unique, so no need to check for duplicates.
iter = packageNames.iterator();
char oldsw = '\0';
while (iter.hasNext()) {
Index pkg = (Index)(iter.next());
oldsw = emitPackageIndexEntry(pkg, oldsw);
}
}
/**
* Emit an index entry for a package.
* Package names are unique, so no need to check for duplicates.
*/
public char emitPackageIndexEntry(Index pkg, char oldsw) {
char res = oldsw;
// See if we are in a new section of the alphabet
char sw = pkg.name_.charAt(0);
if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
// No need to emit section letters for packages
res = sw;
// Add the named anchor for this new letter
h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
}
// Package names are unique, so no need to check for duplicates.
if (pkg.changeType_ == 0) {
h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + pkg.name_ + "</strike></A><br>");
} else if (pkg.changeType_ == 1) {
h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + pkg.name_ + "</b></A><br>");
} else if (pkg.changeType_ == 2) {
h_.writeText("<A HREF=\"pkg_" + pkg.name_ + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + pkg.name_ + "</A><br>");
}
return res;
}
/**
* Emit all the entries and links for the given iterator
* to their respective files.
*/
public void emitIndexEntries(Iterator iter) {
char oldsw = '\0';
int multipleMarker = 0;
Index currIndex = null; // The entry which is emitted
while (iter.hasNext()) {
// The next entry after the current one
Index nextIndex = (Index)(iter.next());
if (currIndex == null) {
currIndex = nextIndex; // Prime the pump
} else {
if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
// It's a duplicate index, so emit the name and then
// the indented entries
if (multipleMarker == 0)
multipleMarker = 1; // Start of a duplicate index
else if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
} else {
if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
multipleMarker = 0; // Not in a duplicate index any more
}
currIndex = nextIndex;
}
}
// Emit the last entry left in currIndex
if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
if (currIndex != null)
oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
}
/**
* Whether to log all missing @since tags to a file or not.
* If false, just warn the user.
*/
public static boolean logMissingSinces = true;
/** The file used to output details of missing @since tags. */
public static PrintWriter missingSincesFile = null;
/**
* Emit elements in the given iterator which were added and
* missing @since tags.
*/
public void emitMissingSinces(Iterator iter) {
// if (!logMissingSinces)
// return;
if (missingSincesFile == null) {
String sinceFileName = h_.outputDir + JDiff.DIR_SEP + "missingSinces.txt";
try {
FileOutputStream fos = new FileOutputStream(sinceFileName);
missingSincesFile = new PrintWriter(fos);
} catch (IOException e) {
System.out.println("IO Error while attempting to create " + sinceFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
while (iter.hasNext()) {
Index currIndex = (Index)(iter.next());
// Only display information about added elements
if (currIndex.changeType_ != 1)
continue;
String programElementType = currIndex.ename_;
String details = null;
if (programElementType.compareTo("class") == 0) {
details = currIndex.pkgName_ + "." + currIndex.name_;
if (currIndex.isInterface_)
details = details + " Interface";
else
details = details + " Class";
} else if (programElementType.compareTo("constructor") == 0) {
details = currIndex.pkgName_ + "." + currIndex.name_ + " Constructor (" + currIndex.type_ + ")";
} else if (programElementType.compareTo("method") == 0) {
details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Method " + currIndex.name_ + "(" + currIndex.type_ + ")";
} else if (programElementType.compareTo("field") == 0) {
details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Field " + currIndex.name_;
} else {
System.out.println("Error: unknown program element type");
System.exit(3);
}
if (currIndex.doc_ == null) {
if (logMissingSinces)
missingSincesFile.println("NO DOC BLOCK: " + details);
else
System.out.println("Warning: the doc block for the new element: " + details + " is missing, so there is no @since tag");
} else if (currIndex.doc_.indexOf("@since") != -1) {
if (logMissingSinces)
missingSincesFile.println("OK: " + details);
} else {
if (logMissingSinces)
missingSincesFile.println("MISSING @SINCE TAG: " + details);
else
System.out.println("Warning: the doc block for the new element: " + details + " is missing an @since tag");
}
}
}
/**
* Emit a single entry and the link to its file.
*
* @param programElementType "Class", "Constructor",
* "Method", or "Field".
*/
public char emitIndexEntry(Index currIndex, char oldsw, int multipleMarker) {
String programElementType = currIndex.ename_;
if (programElementType.compareTo("class") == 0) {
return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
} else if (programElementType.compareTo("constructor") == 0) {
return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
} else if (programElementType.compareTo("method") == 0) {
return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
} else if (programElementType.compareTo("field") == 0) {
return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
} else {
System.out.println("Error: unknown program element type");
System.exit(3);
}
return '\0';
}
/** Emit the index of classes, which appears in the bottom left frame. */
public void emitClassesIndex(APIDiff apiDiff, int indexType) {
// Add all the names of classes to a new list, to be sorted later
classNames = new ArrayList(); // Index[]
boolean hasRemovals = false;
boolean hasAdditions = false;
boolean hasChanges = false;
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
if (pkgDiff.classesRemoved.size() != 0)
hasRemovals = true;
if (pkgDiff.classesAdded.size() != 0)
hasAdditions = true;
if (pkgDiff.classesChanged.size() != 0)
hasChanges = true;
recordDiffs(hasRemovals, hasAdditions, hasChanges);
String pkgName = pkgDiff.name_;
Iterator iterClass = pkgDiff.classesRemoved.iterator();
while ((indexType == 3 || indexType == 0) && iterClass.hasNext()) {
ClassAPI cls = (ClassAPI)(iterClass.next());
classNames.add(new Index(cls.name_, 0, pkgName, cls.isInterface_));
}
iterClass = pkgDiff.classesAdded.iterator();
while ((indexType == 3 || indexType == 1) && iterClass.hasNext()) {
ClassAPI cls = (ClassAPI)(iterClass.next());
Index idx = new Index(cls.name_, 1, pkgName, cls.isInterface_);
idx.doc_ = cls.doc_; // Used for checking @since
classNames.add(idx);
}
iterClass = pkgDiff.classesChanged.iterator();
while ((indexType == 3 || indexType == 2) && iterClass.hasNext()) {
ClassDiff cls = (ClassDiff)(iterClass.next());
classNames.add(new Index(cls.name_, 2, pkgName, cls.isInterface_));
}
}
Collections.sort(classNames);
emitIndexHeader("Classes", indexType, hasRemovals, hasAdditions, hasChanges);
emitIndexEntries(classNames.iterator());
if (indexType == 1)
emitMissingSinces(classNames.iterator());
}
/** Emit an index entry for a class. */
public char emitClassIndexEntry(Index cls, char oldsw,
int multipleMarker) {
char res = oldsw;
String className = cls.pkgName_ + "." + cls.name_;
String classRef = cls.pkgName_ + "." + cls.name_;
boolean isInterface = cls.isInterface_;
// See if we are in a new section of the alphabet
char sw = cls.name_.charAt(0);
if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
res = sw;
// Add the named anchor for this new letter
h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
if (sw == '_')
h_.writeText("<br><b>underscore</b>&nbsp;");
else
h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
generateLetterIndex(classNames, sw, false);
}
// Deal with displaying duplicate indexes
if (multipleMarker == 1) {
h_.writeText("<i>" + cls.name_ + "</i><br>");
}
if (multipleMarker != 0)
h_.indent(INDENT_SIZE);
if (cls.changeType_ == 0) {
// Emit a reference to the correct place for the class in the
// JDiff page for the package
h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt +
"#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + cls.name_ + "</strike></A><br>");
} else if (cls.changeType_ == 1) {
String cn = cls.name_;
if (multipleMarker != 0)
cn = cls.pkgName_;
if (isInterface)
h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b><i>" + cn + "</i></b></A><br>");
else
h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + cn + "</b></A><br>");
} else if (cls.changeType_ == 2) {
String cn = cls.name_;
if (multipleMarker != 0)
cn = cls.pkgName_;
if (isInterface)
h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\"><i>" + cn + "</i></A><br>");
else
h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + cn + "</A><br>");
}
return res;
}
/**
* Emit the index of all constructors, which appears in the bottom left
* frame.
*/
public void emitConstructorsIndex(APIDiff apiDiff, int indexType) {
// Add all the names of constructors to a new list, to be sorted later
ctorNames = new ArrayList(); // Index[]
boolean hasRemovals = false;
boolean hasAdditions = false;
boolean hasChanges = false;
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
String pkgName = pkgDiff.name_;
Iterator iterClass = pkgDiff.classesChanged.iterator();
while (iterClass.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iterClass.next());
if (classDiff.ctorsRemoved.size() != 0)
hasRemovals = true;
if (classDiff.ctorsAdded.size() != 0)
hasAdditions = true;
if (classDiff.ctorsChanged.size() != 0)
hasChanges = true;
recordDiffs(hasRemovals, hasAdditions, hasChanges);
String className = classDiff.name_;
Iterator iterCtor = classDiff.ctorsRemoved.iterator();
while ((indexType == 3 || indexType == 0) && iterCtor.hasNext()) {
ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
ctorNames.add(new Index(className, 0, pkgName, ctor.type_));
}
iterCtor = classDiff.ctorsAdded.iterator();
while ((indexType == 3 || indexType == 1) && iterCtor.hasNext()) {
ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
Index idx = new Index(className, 1, pkgName, ctor.type_);
idx.doc_ = ctor.doc_; // Used for checking @since
ctorNames.add(idx);
}
iterCtor = classDiff.ctorsChanged.iterator();
while ((indexType == 3 || indexType == 2) && iterCtor.hasNext()) {
MemberDiff ctor = (MemberDiff)(iterCtor.next());
ctorNames.add(new Index(className, 2, pkgName, ctor.newType_));
}
}
}
Collections.sort(ctorNames);
emitIndexHeader("Constructors", indexType, hasRemovals, hasAdditions, hasChanges);
emitIndexEntries(ctorNames.iterator());
if (indexType == 1)
emitMissingSinces(ctorNames.iterator());
}
/** Emit an index entry for a constructor. */
public char emitCtorIndexEntry(Index ctor, char oldsw, int multipleMarker) {
char res = oldsw;
String className = ctor.pkgName_ + "." + ctor.name_;
String memberRef = ctor.pkgName_ + "." + ctor.name_;
String type = ctor.type_;
if (type.compareTo("void") == 0)
type = "";
String shownType = HTMLReportGenerator.simpleName(type);
// See if we are in a new section of the alphabet
char sw = ctor.name_.charAt(0);
if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
res = sw;
// Add the named anchor for this new letter
h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
if (sw == '_')
h_.writeText("<br><b>underscore</b>&nbsp;");
else
h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
generateLetterIndex(ctorNames, sw, false);
}
// Deal with displaying duplicate indexes
if (multipleMarker == 1) {
h_.writeText("<i>" + ctor.name_ + "</i><br>");
}
if (multipleMarker != 0)
h_.indent(INDENT_SIZE);
// Deal with each type of difference
// The output displayed for unique or duplicate entries is the same
// for constructors.
if (ctor.changeType_ == 0) {
String commentID = className + ".ctor_removed(" + type + ")";
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + ctor.name_ + "</strike>");
h_.emitTypeWithParens(shownType, false);
h_.writeText("</A></nobr>&nbsp;constructor<br>");
} else if (ctor.changeType_ == 1) {
String commentID = className + ".ctor_added(" + type + ")";
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + ctor.name_ + "</b>");
h_.emitTypeWithParens(shownType, false);
h_.writeText("</A></nobr>&nbsp;constructor<br>");
} else if (ctor.changeType_ == 2) {
String commentID = className + ".ctor_changed(" + type + ")";
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + ctor.name_);
h_.emitTypeWithParens(shownType, false);
h_.writeText("</A></nobr>&nbsp;constructor<br>");
}
return res;
}
/**
* Emit the index of all methods, which appears in the bottom left frame.
*/
public void emitMethodsIndex(APIDiff apiDiff, int indexType) {
// Add all the names of methods to a new list, to be sorted later
methNames = new ArrayList(); // Index[]
boolean hasRemovals = false;
boolean hasAdditions = false;
boolean hasChanges = false;
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
String pkgName = pkgDiff.name_;
Iterator iterClass = pkgDiff.classesChanged.iterator();
while (iterClass.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iterClass.next());
if (classDiff.methodsRemoved.size() != 0)
hasRemovals = true;
if (classDiff.methodsAdded.size() != 0)
hasAdditions = true;
if (classDiff.methodsChanged.size() != 0)
hasChanges = true;
recordDiffs(hasRemovals, hasAdditions, hasChanges);
String className = classDiff.name_;
Iterator iterMeth = classDiff.methodsRemoved.iterator();
while ((indexType == 3 || indexType == 0) && iterMeth.hasNext()) {
MethodAPI meth = (MethodAPI)(iterMeth.next());
methNames.add(new Index(meth.name_, 0, pkgName, className, meth.getSignature()));
}
iterMeth = classDiff.methodsAdded.iterator();
while ((indexType == 3 || indexType == 1) && iterMeth.hasNext()) {
MethodAPI meth = (MethodAPI)(iterMeth.next());
Index idx = new Index(meth.name_, 1, pkgName, className, meth.getSignature());
idx.doc_ = meth.doc_; // Used for checking @since
methNames.add(idx);
}
iterMeth = classDiff.methodsChanged.iterator();
while ((indexType == 3 || indexType == 2) && iterMeth.hasNext()) {
MemberDiff meth = (MemberDiff)(iterMeth.next());
methNames.add(new Index(meth.name_, 2, pkgName, className, meth.newSignature_));
}
}
}
Collections.sort(methNames);
emitIndexHeader("Methods", indexType, hasRemovals, hasAdditions, hasChanges);
emitIndexEntries(methNames.iterator());
if (indexType == 1)
emitMissingSinces(methNames.iterator());
}
/** Emit an index entry for a method. */
public char emitMethodIndexEntry(Index meth, char oldsw,
int multipleMarker) {
char res = oldsw;
String className = meth.pkgName_ + "." + meth.className_;
String memberRef = meth.pkgName_ + "." + meth.className_;
String type = meth.type_;
if (type.compareTo("void") == 0)
type = "";
String shownType = HTMLReportGenerator.simpleName(type);
// See if we are in a new section of the alphabet
char sw = meth.name_.charAt(0);
if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
res = sw;
// Add the named anchor for this new letter
h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
if (sw == '_')
h_.writeText("<br><b>underscore</b>&nbsp;");
else
h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
generateLetterIndex(methNames, sw, false);
}
// Deal with displaying duplicate indexes
if (multipleMarker == 1) {
h_.writeText("<i>" + meth.name_ + "</i><br>");
}
if (multipleMarker != 0)
h_.indent(INDENT_SIZE);
// Deal with each type of difference
if (meth.changeType_ == 0) {
String commentID = className + "." + meth.name_ + "_removed(" + type + ")";
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + meth.name_ + "</strike>");
h_.emitTypeWithParens(shownType, false);
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<strike>");
h_.emitTypeWithParens(shownType, false);
h_.writeText("</strike>&nbsp;in&nbsp;" + className);
}
h_.writeText("</A></nobr><br>");
} else if (meth.changeType_ == 1) {
String commentID = className + "." + meth.name_ + "_added(" + type + ")";
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + meth.name_ + "</b>");
h_.emitTypeWithParens(shownType, false);
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<b>");
h_.emitTypeWithParens(shownType, false);
h_.writeText("</b>&nbsp;in&nbsp;" + className);
}
h_.writeText("</A></nobr><br>");
} else if (meth.changeType_ == 2) {
String commentID = className + "." + meth.name_ + "_changed(" + type + ")";
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + meth.name_);
h_.emitTypeWithParens(shownType, false);
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;");
h_.emitTypeWithParens(shownType, false);
h_.writeText("&nbsp;in&nbsp;" + className);
}
h_.writeText("</A></nobr><br>");
}
return res;
}
/**
* Emit the index of all fields, which appears in the bottom left frame.
*/
public void emitFieldsIndex(APIDiff apiDiff, int indexType) {
// Add all the names of fields to a new list, to be sorted later
fieldNames = new ArrayList(); // Index[]
boolean hasRemovals = false;
boolean hasAdditions = false;
boolean hasChanges = false;
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
String pkgName = pkgDiff.name_;
Iterator iterClass = pkgDiff.classesChanged.iterator();
while (iterClass.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iterClass.next());
if (classDiff.fieldsRemoved.size() != 0)
hasRemovals = true;
if (classDiff.fieldsAdded.size() != 0)
hasAdditions = true;
if (classDiff.fieldsChanged.size() != 0)
hasChanges = true;
recordDiffs(hasRemovals, hasAdditions, hasChanges);
String className = classDiff.name_;
Iterator iterField = classDiff.fieldsRemoved.iterator();
while ((indexType == 3 || indexType == 0) && iterField.hasNext()) {
FieldAPI fld = (FieldAPI)(iterField.next());
fieldNames.add(new Index(fld.name_, 0, pkgName, className, fld.type_, true));
}
iterField = classDiff.fieldsAdded.iterator();
while ((indexType == 3 || indexType == 1) && iterField.hasNext()) {
FieldAPI fld = (FieldAPI)(iterField.next());
Index idx = new Index(fld.name_, 1, pkgName, className, fld.type_, true);
idx.doc_ = fld.doc_; // Used for checking @since
fieldNames.add(idx);
}
iterField = classDiff.fieldsChanged.iterator();
while ((indexType == 3 || indexType == 2) && iterField.hasNext()) {
MemberDiff fld = (MemberDiff)(iterField.next());
fieldNames.add(new Index(fld.name_, 2, pkgName, className, fld.newType_, true));
}
}
}
Collections.sort(fieldNames);
emitIndexHeader("Fields", indexType, hasRemovals, hasAdditions, hasChanges);
emitIndexEntries(fieldNames.iterator());
if (indexType == 1)
emitMissingSinces(fieldNames.iterator());
}
/** Emit an index entry for a field. */
public char emitFieldIndexEntry(Index fld, char oldsw,
int multipleMarker) {
char res = oldsw;
String className = fld.pkgName_ + "." + fld.className_;
String memberRef = fld.pkgName_ + "." + fld.className_;
String type = fld.type_;
if (type.compareTo("void") == 0)
type = "";
String shownType = HTMLReportGenerator.simpleName(type);
// See if we are in a new section of the alphabet
char sw = fld.name_.charAt(0);
if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
res = sw;
// Add the named anchor for this new letter
h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
if (sw == '_')
h_.writeText("<br><b>underscore</b>&nbsp;");
else
h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
generateLetterIndex(fieldNames, sw, false);
}
// Deal with displaying duplicate indexes
if (multipleMarker == 1) {
h_.writeText("<i>" + fld.name_ + "</i><br>");
}
if (multipleMarker != 0) {
// More context than this is helpful here: h_.indent(INDENT_SIZE);
h_.writeText("&nbsp;in&nbsp;");
}
// Deal with each type of difference
if (fld.changeType_ == 0) {
String commentID = className + "." + fld.name_;
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + fld.name_ + "</strike></A>");
h_.writeText("</nobr><br>");
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + className + "</strike></A>");
h_.writeText("</nobr><br>");
}
} else if (fld.changeType_ == 1) {
String commentID = className + "." + fld.name_;
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
h_.writeText("</nobr><br>");
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
h_.writeText("</nobr><br>");
}
} else if (fld.changeType_ == 2) {
String commentID = className + "." + fld.name_;
if (multipleMarker == 0) {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
h_.writeText("</nobr><br>");
} else {
h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
h_.writeText("</nobr><br>");
}
}
return res;
}
/**
* Emit the index of all changes, which appears in the bottom left frame.
* Has to be run after all the other indexes have been written, since it
* uses data from when they are generated.
*/
public void emitAllDiffsIndex(APIDiff apiDiff, int indexType) {
allNames = new ArrayList(); // Index[]
// Add all the changes into one big list, and sort it by name,
// ignoring case
allNames.addAll(packageNames);
allNames.addAll(classNames);
allNames.addAll(ctorNames);
allNames.addAll(methNames);
allNames.addAll(fieldNames);
// Compares two Index objects' names, ignoring case differences.
Collections.sort(allNames);
emitIndexHeader("All Differences", indexType, atLeastOneRemoval,
atLeastOneAddition, atLeastOneChange);
// Tell generateLetterIndex to use allNames as the list when
// using the other methods to generate the indexes.
isAllNames = true;
// Now emit a line for each entry in the list in the appropriate
// format for each program element
Iterator iter = allNames.iterator();
char oldsw = '\0';
int multipleMarker = 0;
Index currIndex = null; // The entry which is emitted
while (iter.hasNext()) {
// The next entry after the current one
Index nextIndex = (Index)(iter.next());
if (currIndex == null) {
currIndex = nextIndex; // Prime the pump
} else {
if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
// It's a duplicate index, so emit the name and then
// the indented entries
if (multipleMarker == 0)
multipleMarker = 1; // Start of a duplicate index
else if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
} else {
if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
multipleMarker = 0; // Not in a duplicate index any more
}
currIndex = nextIndex;
}
}
// Emit the last entry left in currIndex
if (multipleMarker == 1)
multipleMarker = 2; // Inside a duplicate index
if (currIndex != null)
oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
// Tell generateLetterIndex to stop using allNames as the list when
// using the other methods to generate the indexes.
isAllNames = false;
}
/** Call the appropriate *IndexEntry method for each entry. */
public char emitIndexEntryForAny(Index currIndex, char oldsw,
int multipleMarker) {
if (currIndex.ename_.compareTo("package") == 0) {
h_.writeText("<!-- Package " + currIndex.name_ + " -->");
return emitPackageIndexEntry(currIndex, oldsw);
} else if (currIndex.ename_.compareTo("class") == 0) {
h_.writeText("<!-- Class " + currIndex.name_ + " -->");
return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
} else if (currIndex.ename_.compareTo("constructor") == 0) {
h_.writeText("<!-- Constructor " + currIndex.name_ + " -->");
return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
} else if (currIndex.ename_.compareTo("method") == 0) {
h_.writeText("<!-- Method " + currIndex.name_ + " -->");
return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
} else if (currIndex.ename_.compareTo("field") == 0) {
h_.writeText("<!-- Field " + currIndex.name_ + " -->");
return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
}
return '\0';
}
/** The list of all changes for all program elements. */
private List allNames = null; // Index[]
/** The list of all package changes. */
private List packageNames = null; // Index[]
/** The list of all class changes. */
private List classNames = null; // Index[]
/** The list of all constructor changes. */
private List ctorNames = null; // Index[]
/** The list of all method changes. */
private List methNames = null; // Index[]
/** The list of all field changes. */
private List fieldNames = null; // Index[]
/** If set, then use allNames to generate the letter indexes. */
private boolean isAllNames = false;
/**
* If any of the parameters are set, then set the respective atLeastOne
* variable, used to generate the links at the top of the allDiffs index.
* Never unset an atLeastOne variable.
*/
private void recordDiffs(boolean hasRemovals, boolean hasAdditions,
boolean hasChanges) {
if (hasRemovals)
atLeastOneRemoval = true;
if (hasAdditions)
atLeastOneAddition = true;
if (hasChanges)
atLeastOneChange = true;
}
/** Set if there was at least one removal in the entire API. */
private boolean atLeastOneRemoval = false;
/** Set if there was at least one addition in the entire API. */
private boolean atLeastOneAddition = false;
/** Set if there was at least one change in the entire API. */
private boolean atLeastOneChange = false;
/**
* The number of non-breaking spaces to indent a duplicate indexes'
* entries by.
*/
private final int INDENT_SIZE = 2;
}
/**
* Class used to produce indexes of packages and classes.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class Index implements Comparable {
/** The name of the program element this Index object represents. */
public String ename_ = null;
/** Name of the changed package, class or member. */
public String name_ = null;
/** Type of change. 0 = remove, 1 = add, 2 = change. */
public int changeType_;
/** Name of the changed package if name_ is a class name. */
public String pkgName_ = null;
/** Set if this class is an interface. */
public boolean isInterface_= false;
/** The doc block of added elements, default is null. */
public String doc_ = null;
/**
* The new member type. For methods, this is the signature.
*/
public String type_ = null;
/**
* The class name. Only used by methods.
*/
public String className_ = null;
/** Constructor for packages. */
public Index(String name, int changeType) {
ename_ = "package";
name_ = name;
changeType_ = changeType;
}
/** Constructor for classes. */
public Index(String name, int changeType, String pkgName, boolean isInterface) {
ename_ = "class";
name_ = name;
changeType_ = changeType;
pkgName_ = pkgName;
isInterface_ = isInterface;
}
/** Constructor for constructors. */
public Index(String name, int changeType, String pkgName, String type) {
ename_ = "constructor";
name_ = name;
changeType_ = changeType;
pkgName_ = pkgName;
type_ = type;
}
/** Constructor for methods. */
public Index(String name, int changeType, String pkgName,
String className, String type) {
ename_ = "method";
name_ = name;
changeType_ = changeType;
pkgName_ = pkgName;
className_ = className;
type_ = type;
}
/**
* Constructor for fields.
*
* The boolean <code>fld</code> is simply there to differentiate this
* constructor from the one for methods.
*/
public Index(String name, int changeType, String pkgName,
String className, String type, boolean fld) {
ename_ = "field";
name_ = name;
changeType_ = changeType;
pkgName_ = pkgName;
className_ = className;
type_ = type;
}
/** Compare two Index objects by their simple names, ignoring case. */
public int compareTo(Object o) {
return name_.compareToIgnoreCase(((Index)o).name_);
}
}
package jdiff;
import java.util.*;
import java.io.*;
/**
* Emit HTML based on the changes between two sets of APIs.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class HTMLReportGenerator {
/** Default constructor. */
public HTMLReportGenerator() {
}
/** The Comments object for existing comments. */
// private Comments existingComments_ = null;
/**
* The Comments object for freshly regenerated comments.
* This is populated during the generation of the report,
* and should be like existingComments_ but with unused comments
* marked as such, so that they can be commented out in XML when
* the new comments are written out to the comments file.
*/
private Comments newComments_ = null;
/**
* Accessor method for the freshly generated Comments object.
* The list of comments is sorted before the object is returned.
*/
public Comments getNewComments() {
Collections.sort(newComments_.commentsList_);
return newComments_;
}
/** Generate the report. */
public void generate(APIComparator comp) {//, Comments existingComments) {
String fullReportFileName = reportFileName;
if (outputDir != null)
fullReportFileName = outputDir + JDiff.DIR_SEP + reportFileName;
System.out.println("JDiff: generating HTML report into the file '" + fullReportFileName + reportFileExt + "' and the subdirectory '" + fullReportFileName + "'");
// May be null if no comments file exists yet
// existingComments_ = existingComments;
// Where the new comments will be placed
newComments_ = new Comments();
// Writing to multiple files, so make sure the subdirectory exists
File opdir = new File(fullReportFileName);
if (!opdir.mkdir() && !opdir.exists()) {
System.out.println("Error: could not create the subdirectory '" + fullReportFileName + "'");
System.exit(3);
}
// Emit the documentation difference files
if (!Diff.noDocDiffs) {
// Documentation differences, one file per package
Diff.emitDocDiffs(fullReportFileName);
}
// This is the top-level summary file, first in the right hand frame
// or linked at the start to if no frames are used.
String changesSummaryName = fullReportFileName + JDiff.DIR_SEP +
reportFileName + "-summary" + reportFileExt;
apiDiff = comp.apiDiff;
try {
FileOutputStream fos = new FileOutputStream(changesSummaryName);
reportFile = new PrintWriter(fos);
writeStartHTMLHeader();
// Write out the title in he HTML header
String oldAPIName = "Old API";
if (apiDiff.oldAPIName_ != null)
oldAPIName = apiDiff.oldAPIName_;
String newAPIName = "New API";
if (apiDiff.newAPIName_ != null)
newAPIName = apiDiff.newAPIName_;
if (windowTitle == null)
writeHTMLTitle("API Differences between " + oldAPIName + " and " + newAPIName);
else
writeHTMLTitle(windowTitle);
writeStyleSheetRef();
writeText("</HEAD>");
writeText("<BODY>");
// Add the nav bar for the summary page
writeNavigationBar(reportFileName + "-summary", null, null,
null, 0, true,
apiDiff.packagesRemoved.size() != 0,
apiDiff.packagesAdded.size() != 0,
apiDiff.packagesChanged.size() != 0);
// Write the title in the body with some formatting
if (docTitle == null) {
writeText("<center>");
writeText("<H1>API Differences</H1>");
writeText("</center>");
writeText("<center>");
writeText("<H2>Between " + oldAPIName + " and " + newAPIName + "</H2>");
writeText("</center>");
} else {
writeText(docTitle);
}
// Write the contents and the other files as well
writeReport(apiDiff);
writeHTMLFooter();
reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + changesSummaryName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
// Now generate all the other files for multiple frames.
//
// The top-level changes.html frames file where everything starts.
String tln = fullReportFileName + reportFileExt;
// The file for the top-left frame.
String tlf = fullReportFileName + JDiff.DIR_SEP +
"jdiff_topleftframe" + reportFileExt;
// The default file for the bottom-left frame is the one with the
// most information in it.
String allDiffsIndexName = fullReportFileName + JDiff.DIR_SEP +
"alldiffs_index";
// Other indexes for the bottom-left frame.
String packagesIndexName = fullReportFileName + JDiff.DIR_SEP +
"packages_index";
String classesIndexName = fullReportFileName + JDiff.DIR_SEP +
"classes_index";
String constructorsIndexName = fullReportFileName + JDiff.DIR_SEP +
"constructors_index";
String methodsIndexName = fullReportFileName + JDiff.DIR_SEP +
"methods_index";
String fieldsIndexName = fullReportFileName + JDiff.DIR_SEP +
"fields_index";
HTMLFiles hf = new HTMLFiles(this);
hf.emitTopLevelFile(tln, apiDiff);
hf.emitTopLeftFile(tlf);
hf.emitHelp(fullReportFileName, apiDiff);
hf.emitStylesheet();
HTMLIndexes h = new HTMLIndexes(this);
h.emitAllBottomLeftFiles(packagesIndexName, classesIndexName,
constructorsIndexName, methodsIndexName,
fieldsIndexName, allDiffsIndexName, apiDiff);
if (doStats) {
// The file for the statistical report.
String sf = fullReportFileName + JDiff.DIR_SEP +
"jdiff_statistics" + reportFileExt;
HTMLStatistics stats = new HTMLStatistics(this);
stats.emitStatistics(sf, apiDiff);
}
}
/**
* Write the HTML report.
*
* The top section describes all the packages added (with links) and
* removed, and the changed packages section has links which takes you
* to a section for each package. This pattern continues for classes and
* constructors, methods and fields.
*/
public void writeReport(APIDiff apiDiff) {
if (incompatibleChangesOnly) {
removeIncompatibleChanges(apiDiff);
}
// Report packages which were removed in the new API
if (apiDiff.packagesRemoved.size() != 0) {
writeTableStart("Removed Packages", 2);
Iterator iter = apiDiff.packagesRemoved.iterator();
while (iter.hasNext()) {
PackageAPI pkgAPI = (PackageAPI)(iter.next());
String pkgName = pkgAPI.name_;
if (trace) System.out.println("Package " + pkgName + " was removed.");
writePackageTableEntry(pkgName, 0, pkgAPI.doc_, false);
}
writeTableEnd();
}
// Report packages which were added in the new API
if (!incompatibleChangesOnly && apiDiff.packagesAdded.size() != 0) {
writeTableStart("Added Packages", 2);
Iterator iter = apiDiff.packagesAdded.iterator();
while (iter.hasNext()) {
PackageAPI pkgAPI = (PackageAPI)(iter.next());
String pkgName = pkgAPI.name_;
if (trace) System.out.println("Package " + pkgName + " was added.");
writePackageTableEntry(pkgName, 1, pkgAPI.doc_, false);
}
writeTableEnd();
}
// Report packages which were changed in the new API
if (apiDiff.packagesChanged.size() != 0) {
// Emit a table of changed packages, with links to the file
// for each package.
writeTableStart("Changed Packages", 3);
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
String pkgName = pkgDiff.name_;
if (trace) System.out.println("Package " + pkgName + " was changed.");
writePackageTableEntry(pkgName, 2, null, false);
}
writeTableEnd();
writeText("<!-- End of API section -->");
// Now emit a separate file for each changed package.
writeText("<!-- Start of packages section -->");
PackageDiff[] pkgDiffs = new PackageDiff[apiDiff.packagesChanged.size()];
pkgDiffs = (PackageDiff[])apiDiff.packagesChanged.toArray(pkgDiffs);
for (int i = 0; i < pkgDiffs.length; i++) {
reportChangedPackage(pkgDiffs, i);
}
}
}
private void removeIncompatibleChanges(APIDiff apiDiff) {
for (Iterator iter = apiDiff.packagesChanged.iterator(); iter.hasNext();) {
PackageDiff pkgDiff = (PackageDiff) (iter.next());
for (Iterator i = pkgDiff.classesChanged.iterator(); i.hasNext();) {
ClassDiff classDiff = (ClassDiff) i.next();
boolean hasCtors = classDiff.ctorsRemoved.size() != 0
|| classDiff.ctorsChanged.size() != 0;
boolean hasMethods = classDiff.methodsRemoved.size() != 0
|| classDiff.methodsChanged.size() != 0;
boolean hasFields = classDiff.fieldsRemoved.size() != 0
|| classDiff.fieldsChanged.size() != 0;
if (!(hasCtors || hasMethods || hasFields
|| classDiff.inheritanceChange_ != null
|| classDiff.modifiersChange_ != null)) {
i.remove();
}
}
if (pkgDiff.classesChanged.isEmpty()
&& pkgDiff.classesRemoved.isEmpty()) {
iter.remove();
}
}
}
/**
* Write out the details of a changed package in a separate file.
*/
public void reportChangedPackage(PackageDiff[] pkgDiffs, int pkgIndex) {
PackageDiff pkgDiff = pkgDiffs[pkgIndex];
String pkgName = pkgDiff.name_;
PrintWriter oldReportFile = null;
oldReportFile = reportFile;
String localReportFileName = null;
try {
// Prefix package files with pkg_ because there may be a class
// with the same name.
localReportFileName = reportFileName + JDiff.DIR_SEP + "pkg_" + pkgName + reportFileExt;
if (outputDir != null)
localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName;
FileOutputStream fos = new FileOutputStream(localReportFileName);
reportFile = new PrintWriter(fos);
writeStartHTMLHeader();
writeHTMLTitle(pkgName);
writeStyleSheetRef();
writeText("</HEAD>");
writeText("<BODY>");
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + localReportFileName);
System.out.println("Error: "+ e.getMessage());
System.exit(1);
}
String pkgRef = pkgName;
pkgRef = pkgRef.replace('.', '/');
pkgRef = newDocPrefix + pkgRef + "/package-summary";
// A link to the package in the new API
String linkedPkgName = "<A HREF=\"" + pkgRef + ".html\" target=\"_top\"><tt>" + pkgName + "</tt></A>";
String prevPkgRef = null;
if (pkgIndex != 0) {
prevPkgRef = "pkg_" + pkgDiffs[pkgIndex-1].name_ + reportFileExt;
}
// Create the HTML link to the next package
String nextPkgRef = null;
if (pkgIndex < pkgDiffs.length - 1) {
nextPkgRef = "pkg_" + pkgDiffs[pkgIndex+1].name_ + reportFileExt;
}
writeSectionHeader("Package " + linkedPkgName, pkgName,
prevPkgRef, nextPkgRef,
null, 1,
pkgDiff.classesRemoved.size() != 0,
pkgDiff.classesAdded.size() != 0,
pkgDiff.classesChanged.size() != 0);
// Report changes in documentation
if (reportDocChanges && pkgDiff.documentationChange_ != null) {
String pkgDocRef = pkgName + "/package-summary";
pkgDocRef = pkgDocRef.replace('.', '/');
String oldPkgRef = pkgDocRef;
String newPkgRef = pkgDocRef;
if (oldDocPrefix != null)
oldPkgRef = oldDocPrefix + oldPkgRef;
else
oldPkgRef = null;
newPkgRef = newDocPrefix + newPkgRef;
if (oldPkgRef != null)
pkgDiff.documentationChange_ += "<A HREF=\"" + oldPkgRef +
".html#package_description\" target=\"_self\"><tt>old</tt></A> to ";
else
pkgDiff.documentationChange_ += "<tt>old</tt> to ";
pkgDiff.documentationChange_ += "<A HREF=\"" + newPkgRef +
".html#package_description\" target=\"_self\"><tt>new</tt></A>. ";
writeText(pkgDiff.documentationChange_);
}
// Report classes which were removed in the new API
if (pkgDiff.classesRemoved.size() != 0) {
// Determine the title for this section
boolean hasClasses = false;
boolean hasInterfaces = false;
Iterator iter = pkgDiff.classesRemoved.iterator();
while (iter.hasNext()) {
ClassAPI classAPI = (ClassAPI)(iter.next());
if (classAPI.isInterface_)
hasInterfaces = true;
else
hasClasses = true;
}
if (hasInterfaces && hasClasses)
writeTableStart("Removed Classes and Interfaces", 2);
else if (!hasInterfaces && hasClasses)
writeTableStart("Removed Classes", 2);
else if (hasInterfaces && !hasClasses)
writeTableStart("Removed Interfaces", 2);
// Emit the table entries
iter = pkgDiff.classesRemoved.iterator();
while (iter.hasNext()) {
ClassAPI classAPI = (ClassAPI)(iter.next());
String className = classAPI.name_;
if (trace) System.out.println("Class/Interface " + className + " was removed.");
writeClassTableEntry(pkgName, className, 0, classAPI.isInterface_, classAPI.doc_, false);
}
writeTableEnd();
}
// Report classes which were added in the new API
if (!incompatibleChangesOnly && pkgDiff.classesAdded.size() != 0) {
// Determine the title for this section
boolean hasClasses = false;
boolean hasInterfaces = false;
Iterator iter = pkgDiff.classesAdded.iterator();
while (iter.hasNext()) {
ClassAPI classAPI = (ClassAPI)(iter.next());
if (classAPI.isInterface_)
hasInterfaces = true;
else
hasClasses = true;
}
if (hasInterfaces && hasClasses)
writeTableStart("Added Classes and Interfaces", 2);
else if (!hasInterfaces && hasClasses)
writeTableStart("Added Classes", 2);
else if (hasInterfaces && !hasClasses)
writeTableStart("Added Interfaces", 2);
// Emit the table entries
iter = pkgDiff.classesAdded.iterator();
while (iter.hasNext()) {
ClassAPI classAPI = (ClassAPI)(iter.next());
String className = classAPI.name_;
if (trace) System.out.println("Class/Interface " + className + " was added.");
writeClassTableEntry(pkgName, className, 1, classAPI.isInterface_, classAPI.doc_, false);
}
writeTableEnd();
}
// Report classes which were changed in the new API
if (pkgDiff.classesChanged.size() != 0) {
// Determine the title for this section
boolean hasClasses = false;
boolean hasInterfaces = false;
Iterator iter = pkgDiff.classesChanged.iterator();
while (iter.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter.next());
if (classDiff.isInterface_)
hasInterfaces = true;
else
hasClasses = true;
}
if (hasInterfaces && hasClasses)
writeTableStart("Changed Classes and Interfaces", 2);
else if (!hasInterfaces && hasClasses)
writeTableStart("Changed Classes", 2);
else if (hasInterfaces && !hasClasses)
writeTableStart("Changed Interfaces", 2);
// Emit a table of changed classes, with links to the file
// for each class.
iter = pkgDiff.classesChanged.iterator();
while (iter.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter.next());
String className = classDiff.name_;
if (trace) System.out.println("Package " + pkgDiff.name_ + ", class/Interface " + className + " was changed.");
writeClassTableEntry(pkgName, className, 2, classDiff.isInterface_, null, false);
}
writeTableEnd();
// Now emit a separate file for each changed class and interface.
ClassDiff[] classDiffs = new ClassDiff[pkgDiff.classesChanged.size()];
classDiffs = (ClassDiff[])pkgDiff.classesChanged.toArray(classDiffs);
for (int k = 0; k < classDiffs.length; k++) {
reportChangedClass(pkgName, classDiffs, k);
}
}
writeSectionFooter(pkgName, prevPkgRef, nextPkgRef, null, 1);
writeHTMLFooter();
reportFile.close();
reportFile = oldReportFile;
}
/**
* Write out the details of a changed class in a separate file.
*/
public void reportChangedClass(String pkgName, ClassDiff[] classDiffs, int classIndex) {
ClassDiff classDiff = classDiffs[classIndex];
String className = classDiff.name_;
PrintWriter oldReportFile = null;
oldReportFile = reportFile;
String localReportFileName = null;
try {
localReportFileName = reportFileName + JDiff.DIR_SEP + pkgName + "." + className + reportFileExt;
if (outputDir != null)
localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName;
FileOutputStream fos = new FileOutputStream(localReportFileName);
reportFile = new PrintWriter(fos);
writeStartHTMLHeader();
writeHTMLTitle(pkgName + "." + className);
writeStyleSheetRef();
writeText("</HEAD>");
writeText("<BODY>");
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + localReportFileName);
System.out.println("Error: "+ e.getMessage());
System.exit(1);
}
String classRef = pkgName + "." + className;
classRef = classRef.replace('.', '/');
if (className.indexOf('.') != -1) {
classRef = pkgName + ".";
classRef = classRef.replace('.', '/');
classRef = newDocPrefix + classRef + className;
} else {
classRef = newDocPrefix + classRef;
}
// A link to the class in the new API
String linkedClassName = "<A HREF=\"" + classRef + ".html\" target=\"_top\"><tt>" + className + "</tt></A>";
String lcn = pkgName + "." + linkedClassName;
//Links to the previous and next classes
String prevClassRef = null;
if (classIndex != 0) {
prevClassRef = pkgName + "." + classDiffs[classIndex-1].name_ + reportFileExt;
}
// Create the HTML link to the next package
String nextClassRef = null;
if (classIndex < classDiffs.length - 1) {
nextClassRef = pkgName + "." + classDiffs[classIndex+1].name_ + reportFileExt;
}
if (classDiff.isInterface_)
lcn = "Interface " + lcn;
else
lcn = "Class " + lcn;
boolean hasCtors = classDiff.ctorsRemoved.size() != 0 ||
classDiff.ctorsAdded.size() != 0 ||
classDiff.ctorsChanged.size() != 0;
boolean hasMethods = classDiff.methodsRemoved.size() != 0 ||
classDiff.methodsAdded.size() != 0 ||
classDiff.methodsChanged.size() != 0;
boolean hasFields = classDiff.fieldsRemoved.size() != 0 ||
classDiff.fieldsAdded.size() != 0 ||
classDiff.fieldsChanged.size() != 0;
writeSectionHeader(lcn, pkgName, prevClassRef, nextClassRef,
className, 2,
hasCtors, hasMethods, hasFields);
if (classDiff.inheritanceChange_ != null)
writeText("<p><font size=\"+1\">" + classDiff.inheritanceChange_ + "</font>");
// Report changes in documentation
if (reportDocChanges && classDiff.documentationChange_ != null) {
String oldClassRef = null;
if (oldDocPrefix != null) {
oldClassRef = pkgName + "." + className;
oldClassRef = oldClassRef.replace('.', '/');
if (className.indexOf('.') != -1) {
oldClassRef = pkgName + ".";
oldClassRef = oldClassRef.replace('.', '/');
oldClassRef = oldDocPrefix + oldClassRef + className;
} else {
oldClassRef = oldDocPrefix + oldClassRef;
}
}
if (oldDocPrefix != null)
classDiff.documentationChange_ += "<A HREF=\"" + oldClassRef +
".html\" target=\"_self\"><tt>old</tt></A> to ";
else
classDiff.documentationChange_ += "<tt>old</tt> to ";
classDiff.documentationChange_ += "<A HREF=\"" + classRef +
".html\" target=\"_self\"><tt>new</tt></A>. ";
writeText(classDiff.documentationChange_);
}
if (classDiff.modifiersChange_ != null)
writeText("<p><font size=\"+1\">" + classDiff.modifiersChange_ + "</font>");
reportAllCtors(pkgName, classDiff);
reportAllMethods(pkgName, classDiff);
reportAllFields(pkgName, classDiff);
writeSectionFooter(pkgName, prevClassRef, nextClassRef, className, 2);
writeHTMLFooter();
reportFile.close();
reportFile = oldReportFile;
}
/**
* Write out the details of constructors in a class.
*/
public void reportAllCtors(String pkgName, ClassDiff classDiff) {
String className = classDiff.name_;
writeText("<a NAME=\"constructors\"></a>"); // Named anchor
// Report ctors which were removed in the new API
if (classDiff.ctorsRemoved.size() != 0) {
writeTableStart("Removed Constructors", 2);
Iterator iter = classDiff.ctorsRemoved.iterator();
while (iter.hasNext()) {
ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next());
String ctorType = ctorAPI.type_;
if (ctorType.compareTo("void") == 0)
ctorType = "";
String id = className + "(" + ctorType + ")";
if (trace) System.out.println("Constructor " + id + " was removed.");
writeCtorTableEntry(pkgName, className, ctorType, 0, ctorAPI.doc_, false);
}
writeTableEnd();
}
// Report ctors which were added in the new API
if (!incompatibleChangesOnly && classDiff.ctorsAdded.size() != 0) {
writeTableStart("Added Constructors", 2);
Iterator iter = classDiff.ctorsAdded.iterator();
while (iter.hasNext()) {
ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next());
String ctorType = ctorAPI.type_;
if (ctorType.compareTo("void") == 0)
ctorType = "";
String id = className + "(" + ctorType + ")";
if (trace) System.out.println("Constructor " + id + " was added.");
writeCtorTableEntry(pkgName, className, ctorType, 1, ctorAPI.doc_, false);
}
writeTableEnd();
}
// Report ctors which were changed in the new API
if (classDiff.ctorsChanged.size() != 0) {
// Emit a table of changed classes, with links to the section
// for each class.
writeTableStart("Changed Constructors", 3);
Iterator iter = classDiff.ctorsChanged.iterator();
while (iter.hasNext()) {
MemberDiff memberDiff = (MemberDiff)(iter.next());
if (trace) System.out.println("Constructor for " + className +
" was changed from " + memberDiff.oldType_ + " to " +
memberDiff.newType_);
writeCtorChangedTableEntry(pkgName, className, memberDiff);
}
writeTableEnd();
}
}
/**
* Write out the details of methods in a class.
*/
public void reportAllMethods(String pkgName, ClassDiff classDiff) {
writeText("<a NAME=\"methods\"></a>"); // Named anchor
String className = classDiff.name_;
// Report methods which were removed in the new API
if (classDiff.methodsRemoved.size() != 0) {
writeTableStart("Removed Methods", 2);
Iterator iter = classDiff.methodsRemoved.iterator();
while (iter.hasNext()) {
MethodAPI methodAPI = (MethodAPI)(iter.next());
String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")";
if (trace) System.out.println("Method " + methodName + " was removed.");
writeMethodTableEntry(pkgName, className, methodAPI, 0, methodAPI.doc_, false);
}
writeTableEnd();
}
// Report methods which were added in the new API
if (!incompatibleChangesOnly && classDiff.methodsAdded.size() != 0) {
writeTableStart("Added Methods", 2);
Iterator iter = classDiff.methodsAdded.iterator();
while (iter.hasNext()) {
MethodAPI methodAPI = (MethodAPI)(iter.next());
String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")";
if (trace) System.out.println("Method " + methodName + " was added.");
writeMethodTableEntry(pkgName, className, methodAPI, 1, methodAPI.doc_, false);
}
writeTableEnd();
}
// Report methods which were changed in the new API
if (classDiff.methodsChanged.size() != 0) {
// Emit a table of changed methods.
writeTableStart("Changed Methods", 3);
Iterator iter = classDiff.methodsChanged.iterator();
while (iter.hasNext()) {
MemberDiff memberDiff = (MemberDiff)(iter.next());
if (trace) System.out.println("Method " + memberDiff.name_ +
" was changed.");
writeMethodChangedTableEntry(pkgName, className, memberDiff);
}
writeTableEnd();
}
}
/**
* Write out the details of fields in a class.
*/
public void reportAllFields(String pkgName, ClassDiff classDiff) {
writeText("<a NAME=\"fields\"></a>"); // Named anchor
String className = classDiff.name_;
// Report fields which were removed in the new API
if (classDiff.fieldsRemoved.size() != 0) {
writeTableStart("Removed Fields", 2);
Iterator iter = classDiff.fieldsRemoved.iterator();
while (iter.hasNext()) {
FieldAPI fieldAPI = (FieldAPI)(iter.next());
String fieldName = fieldAPI.name_;
if (trace) System.out.println("Field " + fieldName + " was removed.");
writeFieldTableEntry(pkgName, className, fieldAPI, 0, fieldAPI.doc_, false);
}
writeTableEnd();
}
// Report fields which were added in the new API
if (!incompatibleChangesOnly && classDiff.fieldsAdded.size() != 0) {
writeTableStart("Added Fields", 2);
Iterator iter = classDiff.fieldsAdded.iterator();
while (iter.hasNext()) {
FieldAPI fieldAPI = (FieldAPI)(iter.next());
String fieldName = fieldAPI.name_;
if (trace) System.out.println("Field " + fieldName + " was added.");
writeFieldTableEntry(pkgName, className, fieldAPI, 1, fieldAPI.doc_, false);
}
writeTableEnd();
}
// Report fields which were changed in the new API
if (classDiff.fieldsChanged.size() != 0) {
// Emit a table of changed classes, with links to the section
// for each class.
writeTableStart("Changed Fields", 3);
Iterator iter = classDiff.fieldsChanged.iterator();
while (iter.hasNext()) {
MemberDiff memberDiff = (MemberDiff)(iter.next());
if (trace) System.out.println("Field " + pkgName + "." + className + "." + memberDiff.name_ + " was changed from " + memberDiff.oldType_ + " to " + memberDiff.newType_);
writeFieldChangedTableEntry(pkgName, className, memberDiff);
}
writeTableEnd();
}
}
/**
* Write the start of the HTML header, together with the current
* date and time in an HTML comment.
*/
public void writeStartHTMLHeaderWithDate() {
writeStartHTMLHeader(true);
}
/** Write the start of the HTML header. */
public void writeStartHTMLHeader() {
writeStartHTMLHeader(false);
}
/** Write the start of the HTML header. */
public void writeStartHTMLHeader(boolean addDate) {
writeText("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
writeText("<HTML>");
writeText("<HEAD>");
writeText("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
writeText("<!-- Generated by the JDiff Javadoc doclet -->");
writeText("<!-- (" + JDiff.jDiffLocation + ") -->");
if (addDate)
writeText("<!-- on " + new Date() + " -->");
writeText("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
writeText("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
}
/** Write the HTML title */
public void writeHTMLTitle(String title) {
writeText("<TITLE>");
writeText(title);
writeText("</TITLE>");
}
/**
* Write the HTML style sheet reference for files in the subdirectory.
*/
public void writeStyleSheetRef() {
writeStyleSheetRef(false);
}
/**
* Write the HTML style sheet reference. If inSameDir is set, don't add
* "../" to the location.
*/
public void writeStyleSheetRef(boolean inSameDir) {
if (inSameDir)
writeText("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"stylesheet-jdiff.css\" TITLE=\"Style\">");
else
writeText("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"" + "../" + "stylesheet-jdiff.css\" TITLE=\"Style\">");
// This doesn't work in non-windows browsers, so have to change the stylesheet
// writeText("<!-- Override the color choice for the navigation bar -->");
// writeText("<STYLE>");
// //writeText(".NavBarCell1 { background-color:#FFFF99;} /* palegoldenrod */");
// writeText(".NavBarCell1 { background-color:#FFFFCC;} /* */");
// writeText("</STYLE>");
}
/** Write the HTML footer. */
public void writeHTMLFooter() {
writeText("</BODY>");
writeText("</HTML>");
}
/**
* Write a section header, which includes a navigation bar.
*
* @param title Title of the header. Contains any links necessary.
* @param packageName The name of the current package, with no slashes or
* links in it. May be null
* @param prevElemLink An HTML link to the previous element (a package or
* class). May be null.
* @param nextElemLink An HTML link to the next element (a package or
* class). May be null.
* @param className The name of the current class, with no slashes or
* links in it. May be null.
* @param level 0 = overview, 1 = package, 2 = class/interface
*/
public void writeSectionHeader(String title, String packageName,
String prevElemLink, String nextElemLink,
String className, int level,
boolean hasRemovals,
boolean hasAdditions,
boolean hasChanges) {
writeNavigationBar(packageName, prevElemLink, nextElemLink,
className, level, true,
hasRemovals, hasAdditions, hasChanges);
if (level != 0) {
reportFile.println("<H2>");
reportFile.println(title);
reportFile.println("</H2>");
}
}
/**
* Write a section footer, which includes a navigation bar.
*
* @param packageName The name of the current package, with no slashes or
* links in it. may be null
* @param prevElemLink An HTML link to the previous element (a package or
* class). May be null.
* @param nextElemLink An HTML link to the next element (a package or
* class). May be null.
* @param className The name of the current class, with no slashes or
* links in it. May be null
* @param level 0 = overview, 1 = package, 2 = class/interface
*/
public void writeSectionFooter(String packageName,
String prevElemLink, String nextElemLink,
String className, int level) {
reportFile.println("<HR>");
writeNavigationBar(packageName, prevElemLink, nextElemLink,
className, level, false,
false, false, false);
}
/**
* Write a navigation bar section header.
*
* @param pkgName The name of the current package, with no slashes or
* links in it.
* @param prevElemLink An HTML link to the previous element (a package or
* class). May be null.
* @param nextElemLink An HTML link to the next element (a package or
* class). May be null.
* @param className The name of the current class, with no slashes or
* links in it. May be null.
* @param level 0 = overview, 1 = package, 2 = class/interface
*/
public void writeNavigationBar(String pkgName,
String prevElemLink, String nextElemLink,
String className, int level,
boolean upperNavigationBar,
boolean hasRemovals, boolean hasAdditions,
boolean hasChanges) {
reportFile.println("<!-- Start of nav bar -->");
reportFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
reportFile.println(" <TR>");
reportFile.println(" <TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
reportFile.println(" <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
reportFile.println(" <TR ALIGN=\"center\" VALIGN=\"top\">");
boolean atOverview = (level == 0);
boolean atPackage = (level == 1);
boolean atClass = (level == 2);
// Always have a link to the Javadoc files
if (atOverview) {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
} else if (atPackage) {
String pkgRef = pkgName;
pkgRef = pkgRef.replace('.', '/');
pkgRef = newDocPrefix + pkgRef + "/package-summary";
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + pkgRef + ".html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
} else if (atClass) {
String classRef = pkgName + "." + className;
classRef = classRef.replace('.', '/');
if (className.indexOf('.') != -1) {
classRef = pkgName + ".";
classRef = classRef.replace('.', '/');
classRef = newDocPrefix + classRef + className;
} else {
classRef = newDocPrefix + classRef;
}
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + classRef + ".html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
}
if (atOverview) {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Overview</B></FONT>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
}
String changesSummaryName = reportFileName + "-summary" + reportFileExt;
if (atPackage) {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + changesSummaryName + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Package</B></FONT>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
}
if (atClass) {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + changesSummaryName + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"pkg_" + pkgName + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Package</B></FONT></A>&nbsp;</TD>");
reportFile.println(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Class</B></FONT>&nbsp;</TD>");
}
if (!Diff.noDocDiffs) {
if (atPackage) {
String id = (String)Diff.firstDiffOutput.get(pkgName + "!package");
if (id != null)
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt + "#" + id + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
else
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT>&nbsp;</TD>");
} else if (atClass) {
String id = (String)Diff.firstDiffOutput.get(pkgName + "." + className + "!class");
if (id != null)
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt + "#" + id + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
else
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT>&nbsp;</TD>");
} else {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
}
}
if (doStats) {
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
}
// Always have a link to the JDiff help file
reportFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
reportFile.println(" </TR>");
reportFile.println(" </TABLE>");
reportFile.println(" </TD>");
// The right hand side title, only added at the top
if (upperNavigationBar) {
reportFile.println(" <TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3><EM><b>Generated by<br><a href=\"" + JDiff.jDiffLocation + "\" class=\"staysblack\" target=\"_top\">JDiff</a></b></EM></TD>");
} else {
reportFile.println(" <TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3></TD>");
}
reportFile.println("</TR>");
// Links for frames and no frames
reportFile.println("<TR>");
// All of the previous and next links, and the frames and non-frames
// links are in one table cell
reportFile.println(" <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
// Display links to the previous and next packages or classes
if (atPackage || atClass) {
String elemName = "CLASS";
if (className == null) {
elemName = "PACKAGE";
}
if (prevElemLink == null) {
reportFile.println("&nbsp;<B>PREV " + elemName + "</B>&nbsp;");
} else {
reportFile.println("&nbsp;<A HREF=\"" + prevElemLink + "\"><B>PREV " + elemName + "</B></A>");
}
if (nextElemLink == null) {
reportFile.println("&nbsp;<B>NEXT " + elemName + "</B>&nbsp;");
} else {
reportFile.println("&nbsp;<A HREF=\"" + nextElemLink + "\"><B>NEXT " + elemName + "</B></A>");
}
reportFile.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
} else {
reportFile.println(" &nbsp;&nbsp;");
}
// Links for frames and non-frames.
reportFile.println(" <A HREF=\"" + "../" + reportFileName + reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A> &nbsp;");
if (className == null) {
if (level == 0) {
reportFile.println(" &nbsp;<A HREF=\"" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
} else {
reportFile.println(" &nbsp;<A HREF=\"pkg_" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
}
} else {
reportFile.println(" &nbsp;<A HREF=\"" + pkgName + "." + className + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
}
// All of the details links are in one table cell
if (atClass) {
// Links to a class page's sections
// The meaning of these three variable is overloaded
boolean hasCtors = hasRemovals;
boolean hasMethods = hasAdditions;
boolean hasFields = hasChanges;
if (hasCtors || hasMethods || hasFields) {
reportFile.println(" <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell3\"><FONT SIZE=\"-2\"> DETAIL: &nbsp;");
if (hasCtors) {
reportFile.println("<a href=\"#constructors\">CONSTRUCTORS</a>&nbsp;|&nbsp;");
} else {
reportFile.println("CONSTRUCTORS&nbsp;|&nbsp;");
}
if (hasMethods) {
reportFile.println("<a href=\"#methods\">METHODS</a>&nbsp;|&nbsp;");
} else {
reportFile.println("METHODS&nbsp;|&nbsp;");
}
if (hasFields) {
reportFile.println("<a href=\"#fields\">FIELDS</a>");
} else {
reportFile.println("FIELDS");
}
reportFile.println(" </FONT></TD>");
} else {
// Make the end of the table line match the length of the top
reportFile.println("<TD BGCOLOR=\"0xFFFFFF\" CLASS=\"NavBarCell3\"></TD>");
}
} else {
// Links to a package page's sections
if (hasRemovals || hasAdditions || hasChanges) {
reportFile.println(" <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell3\"><FONT SIZE=\"-2\"> DETAIL: &nbsp;");
if (hasRemovals) {
reportFile.println("<a href=\"#Removed\">REMOVED</a>&nbsp;|&nbsp;");
} else {
reportFile.println("REMOVED&nbsp;|&nbsp;");
}
if (hasAdditions) {
reportFile.println("<a href=\"#Added\">ADDED</a>&nbsp;|&nbsp;");
} else {
reportFile.println("ADDED&nbsp;|&nbsp;");
}
if (hasChanges) {
reportFile.println("<a href=\"#Changed\">CHANGED</a>");
} else {
reportFile.println("CHANGED");
}
reportFile.println(" </FONT></TD>");
} else {
// Make the end of the table line match the length of the top
reportFile.println("<TD BGCOLOR=\"0xFFFFFF\" CLASS=\"NavBarCell3\"></TD>");
}
}
reportFile.println("</TR>");
reportFile.println("</TABLE>");
reportFile.println("<HR>");
reportFile.println("<!-- End of nav bar -->");
}
/** Write the start of a table. */
public void writeTableStart(String title, int colSpan) {
reportFile.println("<p>");
// Assumes that the first word of the title categorizes the table type
// and that there is a space after the first word in the title
int idx = title.indexOf(' ');
String namedAnchor = title.substring(0, idx);
reportFile.println("<a NAME=\"" + namedAnchor + "\"></a>"); // Named anchor
reportFile.println("<TABLE summary=\"" + title+ "\" BORDER=\"1\" CELLPADDING=\"3\" CELLSPACING=\"0\" WIDTH=\"100%\">");
reportFile.println("<TR BGCOLOR=\"#CCCCFF\" CLASS=\"TableHeadingColor\">");
reportFile.print(" <TD VALIGN=\"TOP\" COLSPAN=" + colSpan + "><FONT SIZE=\"+1\">");
reportFile.println("<B>" + title + "</B></FONT></TD>");
reportFile.println("</TR>");
}
/**
* If a class or package name is considered to be too long for convenient
* display, insert <br> in the middle of it at a period.
*/
public String makeTwoRows(String name) {
if (name.length() < 30)
return name;
int idx = name.indexOf(".", 20);
if (idx == -1)
return name;
int len = name.length();
String res = name.substring(0, idx+1) + "<br>" + name.substring(idx+1, len);
return res;
}
/**
* Write a table entry for a package, with support for links to Javadoc
* for removed packages.
*
* linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file
*/
public void writePackageTableEntry(String pkgName, int linkType,
String possibleComment, boolean useOld) {
if (!useOld) {
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + pkgName + "\"></A>"); // Named anchor
}
String shownPkgName = makeTwoRows(pkgName);
if (linkType == 0) {
if (oldDocPrefix == null) {
// No link
reportFile.print(" " + shownPkgName);
} else {
// Call this method again but this time to emit a link to the
// old program element.
writePackageTableEntry(pkgName, 1, possibleComment, true);
}
} else if (linkType == 1) {
// Link to HTML file for the package
String pkgRef = pkgName;
pkgRef = pkgRef.replace('.', '/');
if (useOld)
pkgRef = oldDocPrefix + pkgRef + "/package-summary";
else
pkgRef = newDocPrefix + pkgRef + "/package-summary";
reportFile.println(" <nobr><A HREF=\"" + pkgRef + ".html\" target=\"_top\"><tt>" + shownPkgName + "</tt></A></nobr>");
} else if (linkType == 2) {
reportFile.println(" <nobr><A HREF=\"pkg_" + pkgName + reportFileExt + "\">" + shownPkgName + "</A></nobr>");
}
if (!useOld) {
reportFile.println(" </TD>");
emitComment(pkgName, possibleComment, linkType);
reportFile.println("</TR>");
}
}
/**
* Write a table entry for a class or interface.
*
* linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file
*/
public void writeClassTableEntry(String pkgName, String className,
int linkType, boolean isInterface,
String possibleComment, boolean useOld) {
if (!useOld) {
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + className + "\"></A>"); // Named anchor
}
String fqName = pkgName + "." + className;
String shownClassName = makeTwoRows(className);
if (linkType == 0) {
if (oldDocPrefix == null) {
// No link
if (isInterface)
reportFile.println(" <I>" + shownClassName + "</I>");
else
reportFile.println(" " + shownClassName);
} else {
writeClassTableEntry(pkgName, className,
1, isInterface,
possibleComment, true);
}
} else if (linkType == 1) {
// Link to HTML file for the class
String classRef = fqName;
// Deal with inner classes
if (className.indexOf('.') != -1) {
classRef = pkgName + ".";
classRef = classRef.replace('.', '/');
if (useOld)
classRef = oldDocPrefix + classRef + className;
else
classRef = newDocPrefix + classRef + className;
} else {
classRef = classRef.replace('.', '/');
if (useOld)
classRef = oldDocPrefix + classRef;
else
classRef = newDocPrefix + classRef;
}
reportFile.print(" <nobr><A HREF=\"" + classRef + ".html\" target=\"_top\"><tt>");
if (isInterface)
reportFile.print("<I>" + shownClassName + "</I>");
else
reportFile.print(shownClassName);
reportFile.println("</tt></A></nobr>");
} else if (linkType == 2) {
reportFile.print(" <nobr><A HREF=\"" + fqName + reportFileExt + "\">");
if (isInterface)
reportFile.print("<I>" + shownClassName + "</I>");
else
reportFile.print(shownClassName);
reportFile.println("</A></nobr>");
}
if (!useOld) {
reportFile.println(" </TD>");
emitComment(fqName, possibleComment, linkType);
reportFile.println("</TR>");
}
}
/**
* Write a table entry for a constructor.
*
* linkType: 0 - no link by default, 1 = link to Javadoc HTML file
*/
public void writeCtorTableEntry(String pkgName, String className,
String type, int linkType,
String possibleComment, boolean useOld) {
String fqName = pkgName + "." + className;
String shownClassName = makeTwoRows(className);
String lt = "removed";
if (linkType ==1)
lt = "added";
String commentID = fqName + ".ctor_" + lt + "(" + type + ")";
if (!useOld) {
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
}
String shortType = simpleName(type);
if (linkType == 0) {
if (oldDocPrefix == null) {
// No link
reportFile.print(" <nobr>" + shownClassName);
emitTypeWithParens(shortType);
reportFile.println("</nobr>");
} else {
writeCtorTableEntry(pkgName, className,
type, 1,
possibleComment, true);
}
} else if (linkType == 1) {
// Link to HTML file for the package
String memberRef = fqName.replace('.', '/');
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
if (useOld) {
// oldDocPrefix is non-null at this point
memberRef = oldDocPrefix + memberRef + className;
} else {
memberRef = newDocPrefix + memberRef + className;
}
} else {
if (useOld) {
// oldDocPrefix is non-null at this point
memberRef = oldDocPrefix + memberRef;
} else {
memberRef = newDocPrefix + memberRef;
}
}
reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className +
"(" + type + ")\" target=\"_top\"><tt>" + shownClassName + "</tt></A>");
emitTypeWithParens(shortType);
reportFile.println("</nobr>");
}
if (!useOld) {
reportFile.println(" </TD>");
emitComment(commentID, possibleComment, linkType);
reportFile.println("</TR>");
}
}
/**
* Write a table entry for a changed constructor.
*/
public void writeCtorChangedTableEntry(String pkgName, String className,
MemberDiff memberDiff) {
String fqName = pkgName + "." + className;
String newSignature = memberDiff.newType_;
if (newSignature.compareTo("void") == 0)
newSignature = "";
String commentID = fqName + ".ctor_changed(" + newSignature + ")";
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
String memberRef = fqName.replace('.', '/');
String shownClassName = makeTwoRows(className);
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
memberRef = newDocPrefix + memberRef + className;
} else {
memberRef = newDocPrefix + memberRef;
}
String newType = memberDiff.newType_;
if (newType.compareTo("void") == 0)
newType = "";
String shortNewType = simpleName(memberDiff.newType_);
// Constructors have the linked name, then the type in parentheses.
reportFile.print(" <nobr><A HREF=\"" + memberRef + ".html#" + className + "(" + newType + ")\" target=\"_top\"><tt>");
reportFile.print(shownClassName);
reportFile.print("</tt></A>");
emitTypeWithParens(shortNewType);
reportFile.println(" </nobr>");
reportFile.println(" </TD>");
// Report changes in documentation
if (reportDocChanges && memberDiff.documentationChange_ != null) {
String oldMemberRef = null;
String oldType = null;
if (oldDocPrefix != null) {
oldMemberRef = pkgName + "." + className;
oldMemberRef = oldMemberRef.replace('.', '/');
if (className.indexOf('.') != -1) {
oldMemberRef = pkgName + ".";
oldMemberRef = oldMemberRef.replace('.', '/');
oldMemberRef = oldDocPrefix + oldMemberRef + className;
} else {
oldMemberRef = oldDocPrefix + oldMemberRef;
}
oldType = memberDiff.oldType_;
if (oldType.compareTo("void") == 0)
oldType = "";
}
if (oldDocPrefix != null)
memberDiff.documentationChange_ += "<A HREF=\"" +
oldMemberRef + ".html#" + className + "(" + oldType +
")\" target=\"_self\"><tt>old</tt></A> to ";
else
memberDiff.documentationChange_ += "<tt>old</tt> to ";
memberDiff.documentationChange_ += "<A HREF=\"" + memberRef +
".html#" + className + "(" + newType +
")\" target=\"_self\"><tt>new</tt></A>.<br>";
}
emitChanges(memberDiff, 0);
emitComment(commentID, null, 2);
reportFile.println("</TR>");
}
/**
* Write a table entry for a method.
*
* linkType: 0 - no link by default, 1 = link to Javadoc HTML file
*/
public void writeMethodTableEntry(String pkgName, String className,
MethodAPI methodAPI, int linkType,
String possibleComment, boolean useOld) {
String fqName = pkgName + "." + className;
String signature = methodAPI.getSignature();
String methodName = methodAPI.name_;
String lt = "removed";
if (linkType ==1)
lt = "added";
String commentID = fqName + "." + methodName + "_" + lt + "(" + signature + ")";
if (!useOld) {
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
}
if (signature.compareTo("void") == 0)
signature = "";
String shortSignature = simpleName(signature);
String returnType = methodAPI.returnType_;
String shortReturnType = simpleName(returnType);
if (linkType == 0) {
if (oldDocPrefix == null) {
// No link
reportFile.print(" <nobr>");
emitType(shortReturnType);
reportFile.print("&nbsp;" + methodName);
emitTypeWithParens(shortSignature);
reportFile.println("</nobr>");
} else {
writeMethodTableEntry(pkgName, className,
methodAPI, 1,
possibleComment, true);
}
} else if (linkType == 1) {
// Link to HTML file for the package
String memberRef = fqName.replace('.', '/');
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
if (useOld) {
// oldDocPrefix is non-null at this point
memberRef = oldDocPrefix + memberRef + className;
} else {
memberRef = newDocPrefix + memberRef + className;
}
} else {
if (useOld) {
// oldDocPrefix is non-null at this point
memberRef = oldDocPrefix + memberRef;
} else {
memberRef = newDocPrefix + memberRef;
}
}
reportFile.print(" <nobr>");
emitType(shortReturnType);
reportFile.print("&nbsp;<A HREF=\"" + memberRef + ".html#" + methodName +
"(" + signature + ")\" target=\"_top\"><tt>" + methodName + "</tt></A>");
emitTypeWithParens(shortSignature);
reportFile.println("</nobr>");
}
if (!useOld) {
reportFile.println(" </TD>");
emitComment(commentID, possibleComment, linkType);
reportFile.println("</TR>");
}
}
/**
* Write a table entry for a changed method.
*/
public void writeMethodChangedTableEntry(String pkgName, String className,
MemberDiff memberDiff) {
String memberName = memberDiff.name_;
// Generally nowhere to break a member name anyway
// String shownMemberName = makeTwoRows(memberName);
String fqName = pkgName + "." + className;
String newSignature = memberDiff.newSignature_;
String commentID = fqName + "." + memberName + "_changed(" + newSignature + ")";
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
String memberRef = fqName.replace('.', '/');
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
memberRef = newDocPrefix + memberRef + className;
} else {
memberRef = newDocPrefix + memberRef;
}
// Javadoc generated HTML has no named anchors for methods
// inherited from other classes, so link to the defining class' method.
// Only copes with non-inner classes.
if (className.indexOf('.') == -1 &&
memberDiff.modifiersChange_ != null &&
memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) {
memberRef = memberDiff.inheritedFrom_;
memberRef = memberRef.replace('.', '/');
memberRef = newDocPrefix + memberRef;
}
String newReturnType = memberDiff.newType_;
String shortReturnType = simpleName(newReturnType);
String shortSignature = simpleName(newSignature);
reportFile.print(" <nobr>");
emitTypeWithNoParens(shortReturnType);
reportFile.print("&nbsp;<A HREF=\"" + memberRef + ".html#" +
memberName + "(" + newSignature + ")\" target=\"_top\"><tt>");
reportFile.print(memberName);
reportFile.print("</tt></A>");
emitTypeWithParens(shortSignature);
reportFile.println(" </nobr>");
reportFile.println(" </TD>");
// Report changes in documentation
if (reportDocChanges && memberDiff.documentationChange_ != null) {
String oldMemberRef = null;
String oldSignature = null;
if (oldDocPrefix != null) {
oldMemberRef = pkgName + "." + className;
oldMemberRef = oldMemberRef.replace('.', '/');
if (className.indexOf('.') != -1) {
oldMemberRef = pkgName + ".";
oldMemberRef = oldMemberRef.replace('.', '/');
oldMemberRef = oldDocPrefix + oldMemberRef + className;
} else {
oldMemberRef = oldDocPrefix + oldMemberRef;
}
oldSignature = memberDiff.oldSignature_;
}
if (oldDocPrefix != null)
memberDiff.documentationChange_ += "<A HREF=\"" +
oldMemberRef + ".html#" + memberName + "(" +
oldSignature + ")\" target=\"_self\"><tt>old</tt></A> to ";
else
memberDiff.documentationChange_ += "<tt>old</tt> to ";
memberDiff.documentationChange_ += "<A HREF=\"" + memberRef +
".html#" + memberName + "(" + newSignature +
")\" target=\"_self\"><tt>new</tt></A>.<br>";
}
emitChanges(memberDiff, 1);
// Get the comment from the parent class if more appropriate
if (memberDiff.modifiersChange_ != null) {
int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from");
if (parentIdx != -1) {
// Change the commentID to pick up the appropriate method
commentID = memberDiff.inheritedFrom_ + "." + memberName +
"_changed(" + newSignature + ")";
}
}
emitComment(commentID, null, 2);
reportFile.println("</TR>");
}
/**
* Write a table entry for a field.
*
* linkType: 0 - no link by default, 1 = link to Javadoc HTML file
*/
public void writeFieldTableEntry(String pkgName, String className,
FieldAPI fieldAPI, int linkType,
String possibleComment, boolean useOld) {
String fqName = pkgName + "." + className;
// Fields can only appear in one table, so no need to specify _added etc
String fieldName = fieldAPI.name_;
// Generally nowhere to break a member name anyway
// String shownFieldName = makeTwoRows(fieldName);
String commentID = fqName + "." + fieldName;
if (!useOld) {
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
}
String fieldType = fieldAPI.type_;
if (fieldType.compareTo("void") == 0)
fieldType = "";
String shortFieldType = simpleName(fieldType);
if (linkType == 0) {
if (oldDocPrefix == null) {
// No link.
reportFile.print(" ");
emitType(shortFieldType);
reportFile.println("&nbsp;" + fieldName);
} else {
writeFieldTableEntry(pkgName, className,
fieldAPI, 1,
possibleComment, true);
}
} else if (linkType == 1) {
// Link to HTML file for the package.
String memberRef = fqName.replace('.', '/');
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
if (useOld)
memberRef = oldDocPrefix + memberRef + className;
else
memberRef = newDocPrefix + memberRef + className;
} else {
if (useOld)
memberRef = oldDocPrefix + memberRef;
else
memberRef = newDocPrefix + memberRef;
}
reportFile.print(" <nobr>");
emitType(shortFieldType);
reportFile.println("&nbsp;<A HREF=\"" + memberRef + ".html#" + fieldName +
"\" target=\"_top\"><tt>" + fieldName + "</tt></A></nobr>");
}
if (!useOld) {
reportFile.println(" </TD>");
emitComment(commentID, possibleComment, linkType);
reportFile.println("</TR>");
}
}
/**
* Write a table entry for a changed field.
*/
public void writeFieldChangedTableEntry(String pkgName, String className,
MemberDiff memberDiff) {
String memberName = memberDiff.name_;
// Generally nowhere to break a member name anyway
// String shownMemberName = makeTwoRows(memberName);
String fqName = pkgName + "." + className;
// Fields have unique names in a class
String commentID = fqName + "." + memberName;
reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
reportFile.println(" <A NAME=\"" + commentID + "\"></A>"); // Named anchor
String memberRef = fqName.replace('.', '/');
// Deal with inner classes
if (className.indexOf('.') != -1) {
memberRef = pkgName + ".";
memberRef = memberRef.replace('.', '/');
memberRef = newDocPrefix + memberRef + className;
} else {
memberRef = newDocPrefix + memberRef;
}
// Javadoc generated HTML has no named anchors for fields
// inherited from other classes, so link to the defining class' field.
// Only copes with non-inner classes.
if (className.indexOf('.') == -1 &&
memberDiff.modifiersChange_ != null &&
memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) {
memberRef = memberDiff.inheritedFrom_;
memberRef = memberRef.replace('.', '/');
memberRef = newDocPrefix + memberRef;
}
String newType = memberDiff.newType_;
String shortNewType = simpleName(newType);
reportFile.print(" <nobr>");
emitTypeWithNoParens(shortNewType);
reportFile.print("&nbsp;<A HREF=\"" + memberRef + ".html#" +
memberName + "\" target=\"_top\"><tt>");
reportFile.print(memberName);
reportFile.print("</tt></A></nobr>");
reportFile.println(" </TD>");
// Report changes in documentation
if (reportDocChanges && memberDiff.documentationChange_ != null) {
String oldMemberRef = null;
if (oldDocPrefix != null) {
oldMemberRef = pkgName + "." + className;
oldMemberRef = oldMemberRef.replace('.', '/');
if (className.indexOf('.') != -1) {
oldMemberRef = pkgName + ".";
oldMemberRef = oldMemberRef.replace('.', '/');
oldMemberRef = oldDocPrefix + oldMemberRef + className;
} else {
oldMemberRef = oldDocPrefix + oldMemberRef;
}
}
if (oldDocPrefix != null)
memberDiff.documentationChange_ += "<A HREF=\"" +
oldMemberRef + ".html#" + memberName + "\" target=\"_self\"><tt>old</tt></A> to ";
else
memberDiff.documentationChange_ += "<tt>old</tt> to ";
memberDiff.documentationChange_ += "<A HREF=\"" + memberRef +
".html#" + memberName + "\" target=\"_self\"><tt>new</tt></A>.<br>";
}
emitChanges(memberDiff, 2);
// Get the comment from the parent class if more appropriate
if (memberDiff.modifiersChange_ != null) {
int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from");
if (parentIdx != -1) {
// Change the commentID to pick up the appropriate method
commentID = memberDiff.inheritedFrom_ + "." + memberName;
}
}
emitComment(commentID, null, 2);
reportFile.println("</TR>");
}
/**
* Emit all changes associated with a MemberDiff as an entry in a table.
*
* @param memberType 0 = ctor, 1 = method, 2 = field
*/
public void emitChanges(MemberDiff memberDiff, int memberType){
reportFile.println(" <TD VALIGN=\"TOP\" WIDTH=\"30%\">");
boolean hasContent = false;
// The type or return type changed
if (memberDiff.oldType_.compareTo(memberDiff.newType_) != 0) {
String shortOldType = simpleName(memberDiff.oldType_);
String shortNewType = simpleName(memberDiff.newType_);
if (memberType == 1) {
reportFile.print("Change in return type from ");
} else {
reportFile.print("Change in type from ");
}
if (shortOldType.compareTo(shortNewType) == 0) {
// The types differ in package name, so use the full name
shortOldType = memberDiff.oldType_;
shortNewType = memberDiff.newType_;
}
emitType(shortOldType);
reportFile.print(" to ");
emitType(shortNewType);
reportFile.println(".<br>");
hasContent = true;
}
// The signatures changed - only used by methods
if (memberType == 1 &&
memberDiff.oldSignature_ != null &&
memberDiff.newSignature_ != null &&
memberDiff.oldSignature_.compareTo(memberDiff.newSignature_) != 0) {
String shortOldSignature = simpleName(memberDiff.oldSignature_);
String shortNewSignature = simpleName(memberDiff.newSignature_);
if (shortOldSignature.compareTo(shortNewSignature) == 0) {
// The signatures differ in package names, so use the full form
shortOldSignature = memberDiff.oldSignature_;
shortNewSignature = memberDiff.newSignature_;
}
if (hasContent)
reportFile.print(" ");
reportFile.print("Change in signature from ");
if (shortOldSignature.compareTo("") == 0)
shortOldSignature = "void";
emitType(shortOldSignature);
reportFile.print(" to ");
if (shortNewSignature.compareTo("") == 0)
shortNewSignature = "void";
emitType(shortNewSignature);
reportFile.println(".<br>");
hasContent = true;
}
// The exceptions are only non-null in methods and constructors
if (memberType != 2 &&
memberDiff.oldExceptions_ != null &&
memberDiff.newExceptions_ != null &&
memberDiff.oldExceptions_.compareTo(memberDiff.newExceptions_) != 0) {
if (hasContent)
reportFile.print(" ");
// If either one of the exceptions has no spaces in it, or is
// equal to "no exceptions", then just display the whole
// exceptions texts.
int spaceInOld = memberDiff.oldExceptions_.indexOf(" ");
if (memberDiff.oldExceptions_.compareTo("no exceptions") == 0)
spaceInOld = -1;
int spaceInNew = memberDiff.newExceptions_.indexOf(" ");
if (memberDiff.newExceptions_.compareTo("no exceptions") == 0)
spaceInNew = -1;
if (spaceInOld == -1 || spaceInNew == -1) {
reportFile.print("Change in exceptions thrown from ");
emitException(memberDiff.oldExceptions_);
reportFile.print(" to " );
emitException(memberDiff.newExceptions_);
reportFile.println(".<br>");
} else {
// Too many exceptions become unreadable, so just show the
// individual changes. Catch the case where exceptions are
// just reordered.
boolean firstChange = true;
int numRemoved = 0;
StringTokenizer stOld = new StringTokenizer(memberDiff.oldExceptions_, ", ");
while (stOld.hasMoreTokens()) {
String oldException = stOld.nextToken();
if (!memberDiff.newExceptions_.startsWith(oldException) &&
!(memberDiff.newExceptions_.indexOf(", " + oldException) != -1)) {
if (firstChange) {
reportFile.print("Change in exceptions: ");
firstChange = false;
}
if (numRemoved != 0)
reportFile.print(", ");
emitException(oldException);
numRemoved++;
}
}
if (numRemoved == 1)
reportFile.print(" was removed.");
else if (numRemoved > 1)
reportFile.print(" were removed.");
int numAdded = 0;
StringTokenizer stNew = new StringTokenizer(memberDiff.newExceptions_, ", ");
while (stNew.hasMoreTokens()) {
String newException = stNew.nextToken();
if (!memberDiff.oldExceptions_.startsWith(newException) &&
!(memberDiff.oldExceptions_.indexOf(", " + newException) != -1)) {
if (firstChange) {
reportFile.print("Change in exceptions: ");
firstChange = false;
}
if (numAdded != 0)
reportFile.println(", ");
else
reportFile.println(" ");
emitException(newException);
numAdded++;
}
}
if (numAdded == 1)
reportFile.print(" was added");
else if (numAdded > 1)
reportFile.print(" were added");
else if (numAdded == 0 && numRemoved == 0 && firstChange)
reportFile.print("Exceptions were reordered");
reportFile.println(".<br>");
}
// Note the changes between a comma-separated list of Strings
hasContent = true;
}
if (memberDiff.documentationChange_ != null) {
if (hasContent)
reportFile.print(" ");
reportFile.print(memberDiff.documentationChange_);
hasContent = true;
}
// Last, so no need for a <br>
if (memberDiff.modifiersChange_ != null) {
if (hasContent)
reportFile.print(" ");
reportFile.println(memberDiff.modifiersChange_);
hasContent = true;
}
reportFile.println(" </TD>");
}
/**
* Emit a string which is an exception by surrounding it with
* &lt;code&gt; tags.
* If there is a space in the type, e.g. &quot;String, File&quot;, then
* surround it with parentheses too. Do not add &lt;code&gt; tags or
* parentheses if the String is "no exceptions".
*/
public void emitException(String ex) {
if (ex.compareTo("no exceptions") == 0) {
reportFile.print(ex);
} else {
if (ex.indexOf(' ') != -1) {
reportFile.print("(<code>" + ex + "</code>)");
} else {
reportFile.print("<code>" + ex + "</code>");
}
}
}
/**
* Emit a string which is a type by surrounding it with &lt;code&gt; tags.
* If there is a space in the type, e.g. &quot;String, File&quot;, then
* surround it with parentheses too.
*/
public void emitType(String type) {
if (type.compareTo("") == 0)
return;
if (type.indexOf(' ') != -1) {
reportFile.print("(<code>" + type + "</code>)");
} else {
reportFile.print("<code>" + type + "</code>");
}
}
/**
* Emit a string which is a type by surrounding it with &lt;code&gt; tags.
* Also surround it with parentheses too. Used to display methods'
* parameters.
* Suggestions for where a browser should break the
* text are provided with &lt;br> and &ltnobr> tags.
*/
public static void emitTypeWithParens(String type) {
emitTypeWithParens(type, true);
}
/**
* Emit a string which is a type by surrounding it with &lt;code&gt; tags.
* Also surround it with parentheses too. Used to display methods'
* parameters.
*/
public static void emitTypeWithParens(String type, boolean addBreaks) {
if (type.compareTo("") == 0)
reportFile.print("()");
else {
int idx = type.indexOf(", ");
if (!addBreaks || idx == -1) {
reportFile.print("(<code>" + type + "</code>)");
} else {
// Make the browser break text at reasonable places
String sepType = null;
StringTokenizer st = new StringTokenizer(type, ", ");
while (st.hasMoreTokens()) {
String p = st.nextToken();
if (sepType == null)
sepType = p;
else
sepType += ",</nobr> " + p + "<nobr>";
}
reportFile.print("(<code>" + sepType + "<nobr></code>)");
}
}
}
/**
* Emit a string which is a type by surrounding it with &lt;code&gt; tags.
* Do not surround it with parentheses. Used to display methods' return
* types and field types.
*/
public static void emitTypeWithNoParens(String type) {
if (type.compareTo("") != 0)
reportFile.print("<code>" + type + "</code>");
}
/**
* Return a String with the simple names of the classes in fqName.
* &quot;java.lang.String&quot; becomes &quot;String&quot;,
* &quotjava.lang.String, java.io.File&quot becomes &quotString, File&quot;
* and so on. If fqName is null, return null. If fqName is &quot;&quot;,
* return &quot;&quot;.
*/
public static String simpleName(String fqNames) {
if (fqNames == null)
return null;
String res = "";
boolean hasContent = false;
// We parse the string step by step to ensure we take
// fqNames that contains generics parameter in a whole.
ArrayList<String> fqNamesList = new ArrayList<String>();
int genericParametersDepth = 0;
StringBuffer buffer = new StringBuffer();
for (int i=0; i<fqNames.length(); i++) {
char c = fqNames.charAt(i);
if ('<' == c) {
genericParametersDepth++;
}
if ('>' == c) {
genericParametersDepth--;
}
if (',' != c || genericParametersDepth > 0) {
buffer.append(c);
} else if (',' == c) {
fqNamesList.add(buffer.toString().trim());
buffer = new StringBuffer(buffer.length());
}
}
fqNamesList.add(buffer.toString().trim());
for (String fqName : fqNamesList) {
// Assume this will be used inside a <nobr> </nobr> set of tags.
if (hasContent)
res += ", ";
hasContent = true;
// Look for text within '<' and '>' in case this is a invocation of a generic
int firstBracket = fqName.indexOf('<');
int lastBracket = fqName.lastIndexOf('>');
String genericParameter = null;
if (firstBracket != -1 && lastBracket != -1) {
genericParameter = simpleName(fqName.substring(firstBracket + 1, lastBracket));
fqName = fqName.substring(0, firstBracket);
}
int lastDot = fqName.lastIndexOf('.');
if (lastDot < 0) {
res += fqName; // Already as simple as possible
} else {
res += fqName.substring(lastDot+1);
}
if (genericParameter != null)
res += "&lt;" + genericParameter + "&gt;";
}
return res;
}
/**
* Find any existing comment and emit it. Add the new comment to the
* list of new comments. The first instance of the string "@first" in
* a hand-written comment will be replaced by the first sentence from
* the associated doc block, if such exists. Also replace @link by
* an HTML link.
*
* @param commentID The identifier for this comment.
* @param possibleComment A possible comment from another source.
* @param linkType 0 = remove, 1 = add, 2 = change
*/
public void emitComment(String commentID, String possibleComment,
int linkType) {
if (noCommentsOnRemovals && linkType == 0) {
reportFile.println(" <TD>&nbsp;</TD>");
return;
}
if (noCommentsOnAdditions && linkType == 1) {
reportFile.println(" <TD>&nbsp;</TD>");
return;
}
if (noCommentsOnChanges && linkType == 2) {
reportFile.println(" <TD>&nbsp;</TD>");
return;
}
// We have to use this global hash table because the *Diff classes
// do not store the possible comment from the new *API object.
if (!noCommentsOnChanges && possibleComment == null) {
possibleComment = (String)Comments.allPossibleComments.get(commentID);
}
// Just use the first sentence of the possible comment.
if (possibleComment != null) {
int fsidx = RootDocToXML.endOfFirstSentence(possibleComment, false);
if (fsidx != -1 && fsidx != 0)
possibleComment = possibleComment.substring(0, fsidx+1);
}
// String comment = Comments.getComment(existingComments_, commentID);
// if (comment.compareTo(Comments.placeHolderText) == 0) {
// if (possibleComment != null &&
// possibleComment.indexOf("InsertOtherCommentsHere") == -1)
// reportFile.println(" <TD VALIGN=\"TOP\">" + possibleComment + "</TD>");
// else
// reportFile.println(" <TD>&nbsp;</TD>");
// } else {
// int idx = comment.indexOf("@first");
// if (idx == -1) {
// reportFile.println(" <TD VALIGN=\"TOP\">" + Comments.convertAtLinks(comment, "", null, null) + "</TD>");
// } else {
// reportFile.print(" <TD VALIGN=\"TOP\">" + comment.substring(0, idx));
// if (possibleComment != null &&
// possibleComment.indexOf("InsertOtherCommentsHere") == -1)
// reportFile.print(possibleComment);
// reportFile.println(comment.substring(idx + 6) + "</TD>");
// }
// }
// SingleComment newComment = new SingleComment(commentID, comment);
// newComments_.addComment(newComment);
}
/** Write the end of a table. */
public void writeTableEnd() {
reportFile.println("</TABLE>");
reportFile.println("&nbsp;");
}
/** Write a newline out. */
public void writeText() {
reportFile.println();
}
/** Write some text out. */
public void writeText(String text) {
reportFile.println(text);
}
/** Emit some non-breaking space for indentation. */
public void indent(int indent) {
for (int i = 0; i < indent; i++)
reportFile.print("&nbsp;");
}
/**
* The name of the file to which the top-level HTML file is written,
* and also the name of the subdirectory where most of the HTML appears,
* and also a prefix for the names of some of the files in that
* subdirectory.
*/
static String reportFileName = "changes";
/**
* The suffix of the file to which the HTML output is currently being
* written.
*/
static String reportFileExt = ".html";
/**
* The file to which the HTML output is currently being written.
*/
static PrintWriter reportFile = null;
/**
* The object which represents the top of the tree of differences
* between two APIs. It is only used indirectly when emitting a
* navigation bar.
*/
static APIDiff apiDiff = null;
/**
* If set, then do not suggest comments for removals from the first
* sentence of the doc block of the old API.
*/
public static boolean noCommentsOnRemovals = false;
/**
* If set, then do not suggest comments for additions from the first
* sentence of the doc block of the new API.
*/
public static boolean noCommentsOnAdditions = false;
/**
* If set, then do not suggest comments for changes from the first
* sentence of the doc block of the new API.
*/
public static boolean noCommentsOnChanges = false;
/**
* If set, then report changes in documentation (Javadoc comments)
* between the old and the new API. The default is that this is not set.
*/
public static boolean reportDocChanges = false;
/**
* If set, then only report incompatible changes
* between the old and the new API. The default is that this is not set.
*/
public static boolean incompatibleChangesOnly = false;
/**
* Define the prefix for HTML links to the existing set of Javadoc-
* generated documentation for the new API. E.g. For J2SE1.3.x, use
* "http://java.sun.com/j2se/1.3/docs/api/"
*/
public static String newDocPrefix = "../";
/**
* Define the prefix for HTML links to the existing set of Javadoc-
* generated documentation for the old API.
*/
public static String oldDocPrefix = null;
/** To generate statistical output, set this to true. */
public static boolean doStats = false;
/**
* The destination directory for output files.
*/
public static String outputDir = null;
/**
* The title used on the first page of the report. By default, this is
* &quot;API Differences Between &lt;name of old API&gt; and
* &lt;name of new API&gt;&quot;. It can be
* set by using the -doctitle option.
*/
public static String docTitle = null;
/**
* The browser window title for the report. By default, this is
* &quot;API Differences Between &lt;name of old API&gt; and
* &lt;name of new API&gt;&quot;. It can be
* set by using the -windowtitle option.
*/
public static String windowTitle = null;
/** The desired background color for JDiff tables. */
static final String bgcolor = "#FFFFFF";
/** Set to enable debugging output. */
private static final boolean trace = false;
}
package jdiff;
import java.util.*;
import java.io.*;
/**
* Emit an HTML file containing statistics about the differences.
* Statistical information only appears if the -stats argument is used.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class HTMLStatistics {
/** Constructor. */
public HTMLStatistics(HTMLReportGenerator h) {
h_ = h;
}
/** The HTMLReportGenerator instance used to write HTML. */
private HTMLReportGenerator h_ = null;
/**
* Emit the statistics HTML file.
*/
public void emitStatistics(String filename, APIDiff apiDiff) {
try {
FileOutputStream fos = new FileOutputStream(filename);
h_.reportFile = new PrintWriter(fos);
// Write out the HTML header
h_.writeStartHTMLHeader();
// Write out the title
h_.writeHTMLTitle("JDiff Statistics");
h_.writeStyleSheetRef();
h_.writeText("</HEAD>");
h_.writeText("<BODY>");
// Write a customized navigation bar for the statistics page
h_.writeText("<!-- Start of nav bar -->");
h_.writeText("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
h_.writeText("<TR>");
h_.writeText("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
h_.writeText(" <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
h_.writeText(" <TR ALIGN=\"center\" VALIGN=\"top\">");
// Always have a link to the Javadoc files
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
if (!Diff.noDocDiffs) {
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
}
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Statistics</B></FONT>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
h_.writeText(" </TR>");
h_.writeText(" </TABLE>");
h_.writeText("</TD>");
// The right hand side title
h_.writeText("<TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3><EM><b>Generated by<br><a href=\"" + JDiff.jDiffLocation + "\" class=\"staysblack\" target=\"_top\">JDiff</a></b></EM></TD>");
h_.writeText("</TR>");
// Links for frames and no frames
h_.writeText("<TR>");
h_.writeText(" <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\"></FONT>");
h_.writeText("</TD>");
h_.writeText(" <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
h_.writeText(" <A HREF=\"" + "../" + h_.reportFileName + h_.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A> &nbsp;");
h_.writeText(" &nbsp;<A HREF=\"jdiff_statistics" + h_.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
h_.writeText("</TR>");
h_.writeText("</TABLE>");
h_.writeText("<HR>");
h_.writeText ("<!-- End of nav bar -->");
h_.writeText("<center>");
h_.writeText("<H1>JDiff Statistics</H1>");
h_.writeText("</center>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("The percent change statistic reported for all elements in each API is defined recursively as follows:<br>");
h_.writeText("<pre>");
h_.writeText("Percentage difference = 100 * (added + removed + 2*changed)");
h_.writeText(" -----------------------------------");
h_.writeText(" sum of public elements in BOTH APIs");
h_.writeText("</pre>");
h_.writeText("Where <code>added</code> is the number of packages added, <code>removed</code> is the number of packages removed, and <code>changed</code> is the number of packages changed.");
h_.writeText("This definition is applied recursively for the classes and their program elements, so the value for a changed package will be less than 1, unless every class in that package has changed.");
h_.writeText("The definition ensures that if all packages are removed and all new packages are");
h_.writeText("added, the change will be 100%. Values are rounded here, so a value of 0% indicates a percentage difference of less than 0.5%.");
h_.writeText("<p>The overall difference between the two APIs is approximately " + (int)(apiDiff.pdiff) + "%.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<h3>Sections</h3>");
h_.writeText("<a href=\"#packages\">Packages</a> sorted by percentage difference<br>");
h_.writeText("<a href=\"#classes\">Classes and <i>Interfaces</i></a> sorted by percentage difference<br>");
h_.writeText("<a href=\"#numbers\">Differences</a> by number and type<br>");
h_.writeText("<hr>");
h_.writeText("<a name=\"packages\"></a>");
h_.writeText("<h2>Packages Sorted By Percentage Difference</h2>");
emitPackagesByDiff(apiDiff);
h_.writeText("<hr>");
h_.writeText("<a name=\"classes\"></a>");
h_.writeText("<h2>Classes and <i>Interfaces</i> Sorted By Percentage Difference</h2>");
emitClassesByDiff(apiDiff);
h_.writeText("<hr>");
h_.writeText("<a name=\"numbers\"></a>");
h_.writeText("<h2>Differences By Number and Type</h2>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("The numbers of program elements (packages, classes. constructors, methods and fields) which are recorded as removed, added or changed includes only the highest-level program elements. That is, if a class with two methods was added, the number of methods added does not include those two methods, but the number of classes added does include that class.");
h_.writeText("</BLOCKQUOTE>");
emitNumbersByElement(apiDiff);
h_.writeText("</HTML>");
h_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + filename);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
/**
* Emit all packages sorted by percentage difference, and a histogram
* of the values.
*/
public void emitPackagesByDiff(APIDiff apiDiff) {
Collections.sort(apiDiff.packagesChanged, new ComparePkgPdiffs());
// Write out the table start
h_.writeText("<TABLE summary=\"Packages sorted by percentage difference\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR WIDTH=\"20%\">");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Package</b></FONT></TD>");
h_.writeText("</TR>");
int[] hist = new int[101];
for (int i = 0; i < 101; i++) {
hist[i] = 0;
}
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkg = (PackageDiff)(iter.next());
int bucket = (int)(pkg.pdiff);
hist[bucket]++;
h_.writeText("<TR>");
if (bucket != 0)
h_.writeText(" <TD ALIGN=\"center\">" + bucket + "</TD>");
else
h_.writeText(" <TD ALIGN=\"center\">&lt;1</TD>");
h_.writeText(" <TD><A HREF=\"pkg_" + pkg.name_ + h_.reportFileExt + "\">" + pkg.name_ + "</A></TD>");
h_.writeText("</TR>");
}
h_.writeText("</TABLE>");
// Emit the histogram of the results
h_.writeText("<hr>");
h_.writeText("<p><a name=\"packages_hist\"></a>");
h_.writeText("<TABLE summary=\"Histogram of the package percentage differences\" BORDER=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Frequency</b></FONT></TD>");
h_.writeText(" <TD width=\"300\" ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage Frequency</b></FONT></TD>");
h_.writeText("</TR>");
double total = 0;
for (int i = 0; i < 101; i++) {
total += hist[i];
}
for (int i = 0; i < 101; i++) {
if (hist[i] != 0) {
h_.writeText("<TR>");
h_.writeText(" <TD ALIGN=\"center\">" + i + "</TD>");
h_.writeText(" <TD>" + (hist[i]/total) + "</TD>");
h_.writeText(" <TD><img alt=\"|\" src=\"../black.gif\" height=20 width=" + (hist[i]*300/total) + "></TD>");
h_.writeText("</TR>");
}
}
// Repeat the data in a format which is easier for spreadsheets
h_.writeText("<!-- START_PACKAGE_HISTOGRAM");
for (int i = 0; i < 101; i++) {
if (hist[i] != 0) {
h_.writeText(i + "," + (hist[i]/total));
}
}
h_.writeText("END_PACKAGE_HISTOGRAM -->");
h_.writeText("</TABLE>");
}
/**
* Emit all classes sorted by percentage difference, and a histogram
* of the values..
*/
public void emitClassesByDiff(APIDiff apiDiff) {
// Add all the changed classes to a list
List allChangedClasses = new ArrayList();
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkg = (PackageDiff)(iter.next());
if (pkg.classesChanged != null) {
// Add the package name to the class name
List cc = new ArrayList(pkg.classesChanged);
Iterator iter2 = cc.iterator();
while (iter2.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter2.next());
classDiff.name_ = pkg.name_ + "." + classDiff.name_;
}
allChangedClasses.addAll(cc);
}
}
Collections.sort(allChangedClasses, new CompareClassPdiffs());
// Write out the table start
h_.writeText("<TABLE summary=\"Classes sorted by percentage difference\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR WIDTH=\"20%\">");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Class or <i>Interface</i></b></FONT></TD>");
h_.writeText("</TR>");
int[] hist = new int[101];
for (int i = 0; i < 101; i++) {
hist[i] = 0;
}
iter = allChangedClasses.iterator();
while (iter.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter.next());
int bucket = (int)(classDiff.pdiff);
hist[bucket]++;
h_.writeText("<TR>");
if (bucket != 0)
h_.writeText(" <TD ALIGN=\"center\">" + bucket + "</TD>");
else
h_.writeText(" <TD ALIGN=\"center\">&lt;1</TD>");
h_.writeText(" <TD><A HREF=\"" + classDiff.name_ + h_.reportFileExt + "\">");
if (classDiff.isInterface_)
h_.writeText("<i>" + classDiff.name_ + "</i></A></TD>");
else
h_.writeText(classDiff.name_ + "</A></TD>");
h_.writeText("</TR>");
}
h_.writeText("</TABLE>");
// Emit the histogram of the results
h_.writeText("<hr>");
h_.writeText("<p><a name=\"classes_hist\"></a>");
h_.writeText("<TABLE summary=\"Histogram of the class percentage differences\" BORDER=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
h_.writeText(" <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Frequency</b></FONT></TD>");
h_.writeText(" <TD width=\"300\" ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage Frequency</b></FONT></TD>");
h_.writeText("</TR>");
double total = 0;
for (int i = 0; i < 101; i++) {
total += hist[i];
}
for (int i = 0; i < 101; i++) {
if (hist[i] != 0) {
h_.writeText("<TR>");
h_.writeText(" <TD ALIGN=\"center\">" + i + "</TD>");
h_.writeText(" <TD>" + (hist[i]/total) + "</TD>");
h_.writeText(" <TD><img alt=\"|\" src=\"../black.gif\" height=20 width=" + (hist[i]*300/total) + "></TD>");
h_.writeText("</TR>");
}
}
// Repeat the data in a format which is easier for spreadsheets
h_.writeText("<!-- START_CLASS_HISTOGRAM");
for (int i = 0; i < 101; i++) {
if (hist[i] != 0) {
h_.writeText(i + "," + (hist[i]/total));
}
}
h_.writeText("END_CLASS_HISTOGRAM -->");
h_.writeText("</TABLE>");
}
/**
* Emit a table of numbers of removals, additions and changes by
* package, class, constructor, method and field.
*/
public void emitNumbersByElement(APIDiff apiDiff) {
// Local variables to hold the values
int numPackagesRemoved = apiDiff.packagesRemoved.size();
int numPackagesAdded = apiDiff.packagesAdded.size();
int numPackagesChanged = apiDiff.packagesChanged.size();
int numClassesRemoved = 0;
int numClassesAdded = 0;
int numClassesChanged = 0;
int numCtorsRemoved = 0;
int numCtorsAdded = 0;
int numCtorsChanged = 0;
int numMethodsRemoved = 0;
int numMethodsAdded = 0;
int numMethodsChanged = 0;
int numFieldsRemoved = 0;
int numFieldsAdded = 0;
int numFieldsChanged = 0;
int numRemoved = 0;
int numAdded = 0;
int numChanged = 0;
// Calculate the values
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkg = (PackageDiff)(iter.next());
numClassesRemoved += pkg.classesRemoved.size();
numClassesAdded += pkg.classesAdded.size();
numClassesChanged += pkg.classesChanged.size();
Iterator iter2 = pkg.classesChanged.iterator();
while (iter2.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter2.next());
numCtorsRemoved += classDiff.ctorsRemoved.size();
numCtorsAdded += classDiff.ctorsAdded.size();
numCtorsChanged += classDiff.ctorsChanged.size();
numMethodsRemoved += classDiff.methodsRemoved.size();
numMethodsAdded += classDiff.methodsAdded.size();
numMethodsChanged += classDiff.methodsChanged.size();
numFieldsRemoved += classDiff.fieldsRemoved.size();
numFieldsAdded += classDiff.fieldsAdded.size();
numFieldsChanged += classDiff.fieldsChanged.size();
}
}
// Write out the table
h_.writeText("<TABLE summary=\"Number of differences\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
h_.writeText("<TR>");
h_.writeText(" <TD COLSPAN=5 ALIGN=\"center\" NOWRAP bgcolor=\"#EEEEFF\"><FONT size=\"+1\">");
h_.writeText(" <B>Number of Differences</B></FONT></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD>&nbsp;</TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Removals</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Additions</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Changes</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Total</b></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD>Packages</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numPackagesRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numPackagesAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numPackagesChanged + "</TD>");
int numPackages = numPackagesRemoved + numPackagesAdded + numPackagesChanged;
h_.writeText(" <TD ALIGN=\"right\">" + numPackages + "</TD>");
h_.writeText("</TR>");
numRemoved += numPackagesRemoved;
numAdded += numPackagesAdded;
numChanged += numPackagesChanged;
h_.writeText("<TR>");
h_.writeText(" <TD>Classes and <i>Interfaces</i></TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numClassesRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numClassesAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numClassesChanged + "</TD>");
int numClasses = numClassesRemoved + numClassesAdded + numClassesChanged;
h_.writeText(" <TD ALIGN=\"right\">" + numClasses + "</TD>");
h_.writeText("</TR>");
numRemoved += numClassesRemoved;
numAdded += numClassesAdded;
numChanged += numClassesChanged;
h_.writeText("<TR>");
h_.writeText(" <TD>Constructors</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numCtorsRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numCtorsAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numCtorsChanged + "</TD>");
int numCtors = numCtorsRemoved + numCtorsAdded + numCtorsChanged;
h_.writeText(" <TD ALIGN=\"right\">" + numCtors + "</TD>");
h_.writeText("</TR>");
numRemoved += numCtorsRemoved;
numAdded += numCtorsAdded;
numChanged += numCtorsChanged;
h_.writeText("<TR>");
h_.writeText(" <TD>Methods</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numMethodsRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numMethodsAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numMethodsChanged + "</TD>");
int numMethods = numMethodsRemoved + numMethodsAdded + numMethodsChanged;
h_.writeText(" <TD ALIGN=\"right\">" + numMethods + "</TD>");
h_.writeText("</TR>");
numRemoved += numMethodsRemoved;
numAdded += numMethodsAdded;
numChanged += numMethodsChanged;
h_.writeText("<TR>");
h_.writeText(" <TD>Fields</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numFieldsRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numFieldsAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numFieldsChanged + "</TD>");
int numFields = numFieldsRemoved + numFieldsAdded + numFieldsChanged;
h_.writeText(" <TD ALIGN=\"right\">" + numFields + "</TD>");
h_.writeText("</TR>");
numRemoved += numFieldsRemoved;
numAdded += numFieldsAdded;
numChanged += numFieldsChanged;
h_.writeText("<TR>");
h_.writeText(" <TD><b>Total</b></TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numRemoved + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numAdded + "</TD>");
h_.writeText(" <TD ALIGN=\"right\">" + numChanged + "</TD>");
int total = numRemoved + numAdded + numChanged;
h_.writeText(" <TD ALIGN=\"right\">" + total + "</TD>");
h_.writeText("</TR>");
h_.writeText("</TABLE>");
}
}
package jdiff;
import com.sun.javadoc.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*; // Used for invoking Javadoc indirectly
import java.lang.Runtime;
import java.lang.Process;
/**
* Generates HTML describing the changes between two sets of Java source code.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com.
*/
public class JDiff extends Doclet {
public static boolean runningScript = false;
public static LanguageVersion languageVersion(){
return LanguageVersion.JAVA_1_5;
}
/**
* Doclet-mandated start method. Everything begins here.
*
* @param root a RootDoc object passed by Javadoc
* @return true if document generation succeeds
*/
public static boolean start(RootDoc root) {
if (root != null)
System.out.println("JDiff: doclet started ...");
JDiff jd = new JDiff();
return jd.startGeneration(root);
}
/**
* Generate the summary of the APIs.
*
* @param root the RootDoc object passed by Javadoc
* @return true if no problems encountered within JDiff
*/
protected boolean startGeneration(RootDoc newRoot) {
long startTime = System.currentTimeMillis();
// Open the file where the XML representing the API will be stored.
// and generate the XML for the API into it.
if (writeXML) {
RootDocToXML.writeXML(newRoot);
}
if (compareAPIs) {
String tempOldFileName = oldFileName;
if (oldDirectory != null) {
tempOldFileName = oldDirectory;
if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) {
tempOldFileName += JDiff.DIR_SEP;
}
tempOldFileName += oldFileName;
}
// Check the file for the old API exists
File f = new File(tempOldFileName);
if (!f.exists()) {
System.out.println("Error: file '" + tempOldFileName + "' does not exist for the old API");
return false;
}
// Check the file for the new API exists
String tempNewFileName = newFileName;
if (newDirectory != null) {
tempNewFileName = newDirectory;
if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) {
tempNewFileName += JDiff.DIR_SEP;
}
tempNewFileName += newFileName;
}
f = new File(tempNewFileName);
if (!f.exists()) {
System.out.println("Error: file '" + tempNewFileName + "' does not exist for the new API");
return false;
}
// Read the file where the XML representing the old API is stored
// and create an API object for it.
System.out.print("JDiff: reading the old API in from file '" + tempOldFileName + "'...");
// Read the file in, but do not add any text to the global comments
API oldAPI = XMLToAPI.readFile(tempOldFileName, false, oldFileName);
// Read the file where the XML representing the new API is stored
// and create an API object for it.
System.out.print("JDiff: reading the new API in from file '" + tempNewFileName + "'...");
// Read the file in, and do add any text to the global comments
API newAPI = XMLToAPI.readFile(tempNewFileName, true, newFileName);
// Compare the old and new APIs.
APIComparator comp = new APIComparator();
comp.compareAPIs(oldAPI, newAPI);
// Read the file where the XML for comments about the changes between
// the old API and new API is stored and create a Comments object for
// it. The Comments object may be null if no file exists.
int suffix = oldFileName.lastIndexOf('.');
String commentsFileName = "user_comments_for_" + oldFileName.substring(0, suffix);
suffix = newFileName.lastIndexOf('.');
commentsFileName += "_to_" + newFileName.substring(0, suffix) + ".xml";
commentsFileName = commentsFileName.replace(' ', '_');
if (HTMLReportGenerator.outputDir != null)
commentsFileName = HTMLReportGenerator.outputDir + DIR_SEP + commentsFileName;
System.out.println("JDiff: reading the comments in from file '" + commentsFileName + "'...");
Comments existingComments = Comments.readFile(commentsFileName);
if (existingComments == null)
System.out.println(" (the comments file will be created)");
// Generate an HTML report which summarises all the API differences.
HTMLReportGenerator reporter = new HTMLReportGenerator();
reporter.generate(comp);//, existingComments);
// Emit messages about which comments are now unused and
// which are new.
Comments newComments = reporter.getNewComments();
Comments.noteDifferences(existingComments, newComments);
// Write the new comments out to the same file, with unused comments
// now commented out.
System.out.println("JDiff: writing the comments out to file '" + commentsFileName + "'...");
Comments.writeFile(commentsFileName, newComments);
System.out.print("JDiff: finished (took " + (System.currentTimeMillis() - startTime)/1000 + "s");
if (writeXML)
System.out.println(", not including scanning the source files).");
else if (compareAPIs)
System.out.println(").");
if(runningScript) {
// Run the script reporter to see if this module is backwards compatible
ScriptReport sr = new ScriptReport();
int i = sr.run(comp);
System.out.println("Exiting with status code: " + i);
System.exit(i);
}
return true;
}
return false;
}
//
// Option processing
//
/**
* This method is called by Javadoc to
* parse the options it does not recognize. It then calls
* {@link #validOptions} to validate them.
*
* @param option a String containing an option
* @return an int telling how many components that option has
*/
public static int optionLength(String option) {
return Options.optionLength(option);
}
/**
* After parsing the available options using {@link #optionLength},
* Javadoc invokes this method with an array of options-arrays.
*
* @param options an array of String arrays, one per option
* @param reporter a DocErrorReporter for generating error messages
* @return true if no errors were found, and all options are
* valid
*/
public static boolean validOptions(String[][] options,
DocErrorReporter reporter) {
return Options.validOptions(options, reporter);
}
/**
* This method is only called when running JDiff as a standalone
* application, and uses ANT to execute the build configuration in the
* XML configuration file passed in.
*/
public static void main(String[] args) {
if (args.length == 0) {
//showUsage();
System.out.println("Looking for a local 'build.xml' configuration file");
} else if (args.length == 1) {
if (args[0].compareTo("-help") == 0 ||
args[0].compareTo("-h") == 0 ||
args[0].compareTo("?") == 0) {
showUsage();
} else if (args[0].compareTo("-version") == 0) {
System.out.println("JDiff version: " + JDiff.version);
}
return;
}
int rc = runAnt(args);
return;
}
/**
* Display usage information for JDiff.
*/
public static void showUsage() {
System.out.println("usage: java jdiff.JDiff [-version] [-buildfile <XML configuration file>]");
System.out.println("If no build file is specified, the local build.xml file is used.");
}
/**
* Invoke ANT by reflection.
*
* @return The integer return code from running ANT.
*/
public static int runAnt(String[] args) {
String className = null;
Class c = null;
try {
className = "org.apache.tools.ant.Main";
c = Class.forName(className);
} catch (ClassNotFoundException e1) {
System.err.println("Error: ant.jar not found on the classpath");
return -1;
}
try {
Class[] methodArgTypes = new Class[1];
methodArgTypes[0] = args.getClass();
Method mainMethod = c.getMethod("main", methodArgTypes);
Object[] methodArgs = new Object[1];
methodArgs[0] = args;
// The object can be null because the method is static
Integer res = (Integer)mainMethod.invoke(null, methodArgs);
System.gc(); // Clean up after running ANT
return res.intValue();
} catch (NoSuchMethodException e2) {
System.err.println("Error: method \"main\" 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 \"main\" could not be invoked");
e5.printStackTrace();
} catch (Exception e6) {
System.err.println("Error: ");
e6.printStackTrace();
}
System.gc(); // Clean up after running ANT
return -1;
}
/**
* The name of the file where the XML representing the old API is
* stored.
*/
static String oldFileName = "old_java.xml";
/**
* The name of the directory where the XML representing the old API is
* stored.
*/
static String oldDirectory = null;
/**
* The name of the file where the XML representing the new API is
* stored.
*/
static String newFileName = "new_java.xml";
/**
* The name of the directory where the XML representing the new API is
* stored.
*/
static String newDirectory = null;
/** If set, then generate the XML for an API and exit. */
static boolean writeXML = false;
/** If set, then read in two XML files and compare their APIs. */
static boolean compareAPIs = false;
/**
* The file separator for the local filesystem, forward or backward slash.
*/
static String DIR_SEP = System.getProperty("file.separator");
/** Details for where to find JDiff. */
static final String jDiffLocation = "http://www.jdiff.org";
/** Contact email address for the primary JDiff maintainer. */
static final String authorEmail = "mdoar@pobox.com";
/** A description for HTML META tags. */
static final String jDiffDescription = "JDiff is a Javadoc doclet which generates an HTML report of all the packages, classes, constructors, methods, and fields which have been removed, added or changed in any way, including their documentation, when two APIs are compared.";
/** Keywords for HTML META tags. */
static final String jDiffKeywords = "diff, jdiff, javadiff, java diff, java difference, API difference, difference between two APIs, API diff, Javadoc, doclet";
/** The current JDiff version. */
static final String version = "1.1.1";
/** The current JVM version. */
static String javaVersion = System.getProperty("java.version");
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
} //JDiff
package jdiff;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Javadoc;
import org.apache.tools.ant.taskdefs.Javadoc.DocletInfo;
import org.apache.tools.ant.taskdefs.Javadoc.DocletParam;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
/**
* An Ant task to produce a simple JDiff report. More complex reports still
* need parameters that are controlled by the Ant Javadoc task.
*/
public class JDiffAntTask {
public void execute() throws BuildException {
jdiffHome = project.getProperty("JDIFF_HOME");
if (jdiffHome == null || jdiffHome.compareTo("") == 0 |
jdiffHome.compareTo("(not set)") == 0) {
throw new BuildException("Error: invalid JDIFF_HOME property. Set it in the build file to the directory where jdiff is installed");
}
project.log(" JDiff home: " + jdiffHome, Project.MSG_INFO);
jdiffClassPath = jdiffHome + DIR_SEP + "jdiff.jar" +
System.getProperty("path.separator") +
jdiffHome + DIR_SEP + "xerces.jar";
// TODO detect and set verboseAnt
// Create, if necessary, the directory for the JDiff HTML report
if (!destdir.mkdir() && !destdir.exists()) {
throw new BuildException(getDestdir() + " is not a valid directory");
} else {
project.log(" Report location: " + getDestdir() + DIR_SEP
+ "changes.html", Project.MSG_INFO);
}
// Could also output the other parameters used for JDiff here
// Check that there are indeed two projects to compare. If there
// are no directories in the project, let Javadoc do the complaining
if (oldProject == null || newProject == null) {
throw new BuildException("Error: two projects are needed, one <old> and one <new>");
}
/*
// Display the directories being compared, and some name information
if (getVerbose()) {
project.log("Older version: " + oldProject.getName(),
Project.MSG_INFO);
project.log("Included directories for older version:",
Project.MSG_INFO);
DirectoryScanner ds =
oldProject.getDirset().getDirectoryScanner(project);
String[] files = ds.getIncludedDirectories();
for (int i = 0; i < files.length; i++) {
project.log(" " + files[i], Project.MSG_INFO);
}
ds = null;
project.log("Newer version: " + newProject.getName(),
Project.MSG_INFO);
project.log("Included directories for newer version:",
Project.MSG_INFO);
ds = newProject.getDirset().getDirectoryScanner(project);
files = ds.getIncludedDirectories();
for (int i = 0; i < files.length; i++) {
project.log(" " + files[i], Project.MSG_INFO);
}
}
*/
// Call Javadoc twice to generate Javadoc for each project
generateJavadoc(oldProject);
generateJavadoc(newProject);
// Call Javadoc three times for JDiff.
generateXML(oldProject);
generateXML(newProject);
compareXML(oldProject.getName(), newProject.getName());
// Repeat some useful information
project.log(" Report location: " + getDestdir() + DIR_SEP
+ "changes.html", Project.MSG_INFO);
}
/**
* Convenient method to create a Javadoc task, configure it and run it
* to generate the XML representation of a project's source files.
*
* @param proj The current Project
*/
protected void generateXML(ProjectInfo proj) {
String apiname = proj.getName();
Javadoc jd = initJavadoc("Analyzing " + apiname);
jd.setDestdir(getDestdir());
addSourcePaths(jd, proj);
// Tell Javadoc which packages we want to scan.
// JDiff works with packagenames, not sourcefiles.
jd.setPackagenames(getPackageList(proj));
// Create the DocletInfo first so we have a way to use it to add params
DocletInfo dInfo = jd.createDoclet();
jd.setDoclet("jdiff.JDiff");
jd.setDocletPath(new Path(project, jdiffClassPath));
// Now set up some parameters for the JDiff doclet.
DocletParam dp1 = dInfo.createParam();
dp1.setName("-apiname");
dp1.setValue(apiname);
DocletParam dp2 = dInfo.createParam();
dp2.setName("-baseURI");
dp2.setValue("http://www.w3.org");
// Put the generated file in the same directory as the report
DocletParam dp3 = dInfo.createParam();
dp3.setName("-apidir");
dp3.setValue(getDestdir().toString());
// Execute the Javadoc command to generate the XML file.
jd.perform();
}
/**
* Convenient method to create a Javadoc task, configure it and run it
* to compare the XML representations of two instances of a project's
* source files, and generate an HTML report summarizing the differences.
*
* @param oldapiname The name of the older version of the project
* @param newapiname The name of the newer version of the project
*/
protected void compareXML(String oldapiname, String newapiname) {
Javadoc jd = initJavadoc("Comparing versions");
jd.setDestdir(getDestdir());
jd.setPrivate(true);
// Tell Javadoc which files we want to scan - a dummy file in this case
jd.setSourcefiles(jdiffHome + DIR_SEP + "Null.java");
// Create the DocletInfo first so we have a way to use it to add params
DocletInfo dInfo = jd.createDoclet();
jd.setDoclet("jdiff.JDiff");
jd.setDocletPath(new Path(project, jdiffClassPath));
// Now set up some parameters for the JDiff doclet.
DocletParam dp1 = dInfo.createParam();
dp1.setName("-oldapi");
dp1.setValue(oldapiname);
DocletParam dp2 = dInfo.createParam();
dp2.setName("-newapi");
dp2.setValue(newapiname);
// Get the generated XML files from the same directory as the report
DocletParam dp3 = dInfo.createParam();
dp3.setName("-oldapidir");
dp3.setValue(getDestdir().toString());
DocletParam dp4 = dInfo.createParam();
dp4.setName("-newapidir");
dp4.setValue(getDestdir().toString());
// Assume that Javadoc reports already exist in ../"apiname"
DocletParam dp5 = dInfo.createParam();
dp5.setName("-javadocold");
dp5.setValue(".." + DIR_SEP + oldapiname + DIR_SEP);
DocletParam dp6 = dInfo.createParam();
dp6.setName("-javadocnew");
dp6.setValue(".." + DIR_SEP + newapiname + DIR_SEP);
if (getStats()) {
// There are no arguments to this argument
dInfo.createParam().setName("-stats");
// We also have to copy two image files for the stats pages
copyFile(jdiffHome + DIR_SEP + "black.gif",
getDestdir().toString() + DIR_SEP + "black.gif");
copyFile(jdiffHome + DIR_SEP + "background.gif",
getDestdir().toString() + DIR_SEP + "background.gif");
}
if (getDocchanges()) {
// There are no arguments to this argument
dInfo.createParam().setName("-docchanges");
}
if (getIncompatible()) {
// There are no arguments to this argument
dInfo.createParam().setName("-incompatible");
}
if(getScript()) {
dInfo.createParam().setName("-script");
}
// Execute the Javadoc command to compare the two XML files
jd.perform();
}
/**
* Generate the Javadoc for the project. If you want to generate
* the Javadoc report for the project with different parameters from the
* simple ones used here, then use the Javadoc Ant task directly, and
* set the javadoc attribute to the "old" or "new" element.
*
* @param proj The current Project
*/
protected void generateJavadoc(ProjectInfo proj) {
String javadoc = proj.getJavadoc();
if (javadoc != null && javadoc.compareTo("generated") != 0) {
project.log("Configured to use existing Javadoc located in " +
javadoc, Project.MSG_INFO);
return;
}
String apiname = proj.getName();
Javadoc jd = initJavadoc("Javadoc for " + apiname);
jd.setDestdir(new File(getDestdir().toString() + DIR_SEP + apiname));
addSourcePaths(jd, proj);
jd.setPrivate(true);
jd.setPackagenames(getPackageList(proj));
// Execute the Javadoc command to generate a regular Javadoc report
jd.perform();
}
/**
* Create a fresh new Javadoc task object and initialize it.
*
* @param logMsg String which appears as a prefix in the Ant log
* @return The new task.Javadoc object
*/
protected Javadoc initJavadoc(String logMsg) {
Javadoc jd = new Javadoc();
jd.setProject(project); // Vital, otherwise Ant crashes
jd.setTaskName(logMsg);
jd.setSource(getSource()); // So we can set the language version
jd.init();
// Set up some common parameters for the Javadoc task
if (verboseAnt) {
jd.setVerbose(true);
}
return jd;
}
/**
* Add the root directories for the given project to the Javadoc
* sourcepath.
*/
protected void addSourcePaths(Javadoc jd, ProjectInfo proj) {
Vector dirSets = proj.getDirsets();
int numDirSets = dirSets.size();
for (int i = 0; i < numDirSets; i++) {
DirSet dirSet = (DirSet)dirSets.elementAt(i);
jd.setSourcepath(new Path(project, dirSet.getDir(project).toString()));
}
}
/**
* Return the comma-separated list of packages. The list is
* generated from Ant DirSet tasks, and includes all directories
* in a hierarchy, e.g. com, com/acme. com/acme/foo. Duplicates are
* ignored.
*/
protected String getPackageList(ProjectInfo proj) throws BuildException {
String packageList = "";
java.lang.StringBuffer sb = new StringBuffer();
Vector dirSets = proj.getDirsets();
int numDirSets = dirSets.size();
boolean addComma = false;
for (int i = 0; i < numDirSets; i++) {
DirSet dirSet = (DirSet)dirSets.elementAt(i);
DirectoryScanner dirScanner = dirSet.getDirectoryScanner(project);
String[] files = dirScanner.getIncludedDirectories();
for (int j = 0; j < files.length; j++) {
if (!addComma){
addComma = true;
} else {
sb.append(",");
}
sb.append(files[j]);
}
}
packageList = sb.toString();
if (packageList.compareTo("") == 0) {
throw new BuildException("Error: no packages found to scan");
}
project.log(" Package list: " + packageList, Project.MSG_INFO);
return packageList;
}
/**
* Copy a file from src to dst. Also checks that "destdir/changes" exists
*/
protected void copyFile(String src, String dst){
File srcFile = new File(src);
File dstFile = new File(dst);
try {
File reportSubdir = new File(getDestdir().toString() +
DIR_SEP + "changes");
if (!reportSubdir.mkdir() && !reportSubdir.exists()) {
project.log("Warning: unable to create " + reportSubdir,
Project.MSG_WARN);
}
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (java.io.FileNotFoundException fnfe) {
project.log("Warning: unable to copy " + src.toString() +
" to " + dst.toString(), Project.MSG_WARN);
// Discard the exception
} catch (java.io.IOException ioe) {
project.log("Warning: unable to copy " + src.toString() +
" to " + dst.toString(), Project.MSG_WARN);
// Discard the exception
}
}
/**
* The JDiff Ant task does not inherit from an Ant task, such as the
* Javadoc task, though this is usually how most Tasks are
* written. This is because JDiff needs to run Javadoc three times
* (twice for generating XML, once for generating HTML). The
* Javadoc task has not easy way to reset its list of packages, so
* we needed to be able to crate new Javadoc task objects.
*
* Note: Don't confuse this class with the ProjectInfo used by JDiff.
* This Project class is from Ant.
*/
private Project project;
/**
* Used as part of Ant's startup.
*/
public void setProject(Project proj) {
project = proj;
}
/**
* Ferward or backward slash, as appropriate.
*/
static String DIR_SEP = System.getProperty("file.separator");
/**
* JDIFF_HOME must be set as a property in the Ant build file.
* It should be set to the root JDiff directory, ie. the one where
* jdiff.jar is found.
*/
private String jdiffHome = "(not set)";
/**
* The classpath used by Javadoc to find jdiff.jar and xerces.jar.
*/
private String jdiffClassPath = "(not set)";
/* ***************************************************************** */
/* * Objects and methods which are related to attributes * */
/* ***************************************************************** */
/**
* The destination directory for the generated report.
* The default is "./jdiff_report".
*/
private File destdir = new File("jdiff_report");
/**
* Used to store the destdir attribute of the JDiff task XML element.
*/
public void setDestdir(File value) {
this.destdir = value;
}
public File getDestdir() {
return this.destdir;
}
/**
* Increases the JDiff Ant task logging verbosity if set with "yes", "on"
* or true". Default has to be false.
* To increase verbosity of Javadoc, start Ant with -v or -verbose.
*/
private boolean verbose = false;
public void setVerbose(boolean value) {
this.verbose = value;
}
public boolean getVerbose() {
return this.verbose;
}
/**
* Set if ant was started with -v or -verbose
*/
private boolean verboseAnt = false;
/**
* Add the -docchanges argument, to track changes in Javadoc documentation
* as well as changes in classes etc.
*/
private boolean docchanges = false;
public void setDocchanges(boolean value) {
this.docchanges = value;
}
public boolean getDocchanges() {
return this.docchanges;
}
/**
* Add the -incompatible argument, to only report incompatible changes.
*/
private boolean incompatible = false;
public void setIncompatible(boolean value) {
this.incompatible = value;
}
public boolean getIncompatible() {
return this.incompatible;
}
/**
* Add the -script argument
*/
private boolean script = false;
public void setScript(boolean value) {
this.script = value;
}
public boolean getScript() {
return this.script;
}
/**
* Add statistics to the report if set. Default can only be false.
*/
private boolean stats = false;
public void setStats(boolean value) {
this.stats = value;
}
public boolean getStats() {
return this.stats;
}
/**
* Allow the source language version to be specified.
*/
private String source = "1.5"; // Default is 1.5, so generics will work
public void setSource(String source) {
this.source = source;
}
public String getSource() {
return source;
}
/* ***************************************************************** */
/* * Classes and objects which are related to elements * */
/* ***************************************************************** */
/**
* A ProjectInfo-derived object for the older version of the project
*/
private ProjectInfo oldProject = null;
/**
* Used to store the child element named "old", which is under the
* JDiff task XML element.
*/
public void addConfiguredOld(ProjectInfo projInfo) {
oldProject = projInfo;
}
/**
* A ProjectInfo-derived object for the newer version of the project
*/
private ProjectInfo newProject = null;
/**
* Used to store the child element named "new", which is under the
* JDiff task XML element.
*/
public void addConfiguredNew(ProjectInfo projInfo) {
newProject = projInfo;
}
/**
* This class handles the information about a project, whether it is
* the older or newer version.
*
* Note: Don't confuse this class with the Project used by Ant.
* This ProjectInfo class is from local to this task.
*/
public static class ProjectInfo {
/**
* The name of the project. This is used (without spaces) as the
* base of the name of the file which contains the XML representing
* the project.
*/
private String name;
public void setName(String value) {
name = value;
}
public String getName() {
return name;
}
/**
* The location of the Javadoc HTML for this project. Default value
* is "generate", which will cause the Javadoc to be generated in
* a subdirectory named "name" in the task's destdir directory.
*/
private String javadoc;
public void setJavadoc(String value) {
javadoc = value;
}
public String getJavadoc() {
return javadoc;
}
/**
* These are the directories which contain the packages which make
* up the project. Filesets are not supported by JDiff.
*/
private Vector dirsets = new Vector();
public void setDirset(DirSet value) {
dirsets.add(value);
}
public Vector getDirsets() {
return dirsets;
}
/**
* Used to store the child element named "dirset", which is under the
* "old" or "new" XML elements.
*/
public void addDirset(DirSet aDirset) {
setDirset(aDirset);
}
}
}
package jdiff;
import java.util.*;
import com.sun.javadoc.*;
/**
* The changes between two class constructor, method or field members.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class MemberDiff {
/** The name of the member. */
public String name_;
/**
* The old member type. For methods, this is the return type.
*/
public String oldType_ = null;
/**
* The new member type. For methods, this is the return type.
*/
public String newType_ = null;
/** The old signature. Null except for methods. */
public String oldSignature_ = null;
/** The new signature. Null except for methods. */
public String newSignature_ = null;
/**
* The old list of exceptions. Null except for methods and constructors.
*/
public String oldExceptions_ = null;
/**
* The new list of exceptions. Null except for methods and constructors.
*/
public String newExceptions_ = null;
/**
* A string describing the changes in documentation.
*/
public String documentationChange_ = null;
/**
* A string describing the changes in modifiers.
* Changes can be in whether this is abstract, static, final, and in
* its visibility.
* Null if no change.
*/
public String modifiersChange_ = null;
/**
* The class name where the new member is defined.
* Null if no change in inheritance.
*/
public String inheritedFrom_ = null;
/** Default constructor. */
public MemberDiff(String name) {
name_ = name;
}
/** Add a change in the modifiers. */
public void addModifiersChange(String commonModifierChanges) {
if (commonModifierChanges != null) {
if (modifiersChange_ == null)
modifiersChange_ = commonModifierChanges;
else
modifiersChange_ += " " + commonModifierChanges;
}
}
}
package jdiff;
import java.util.*;
/**
* Convert some remove and add operations into change operations.
*
* Once the numbers of members removed and added are known
* we can deduce more information about changes. For instance, if there are
* two methods with the same name, and one or more of them has a
* parameter type change, then this can only be reported as removing
* the old version(s) and adding the new version(s), because there are
* multiple methods with the same name.
*
* However, if only <i>one</i> method with a given name is removed, and
* only <i>one</i> method with the same name is added, we can convert these
* operations to a change operation. For constructors, this is true if
* the types are the same. For fields, the field names have to be the same,
* though this should never occur, since field names are unique.
*
* Another merge which can be made is if two or more methods with the same name
* were marked as removed and added because of changes other than signature.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class MergeChanges {
/**
* Convert some remove and add operations into change operations.
*
* Note that if a single thread modifies a collection directly while it is
* iterating over the collection with a fail-fast iterator, the iterator
* will throw java.util.ConcurrentModificationException
*/
public static void mergeRemoveAdd(APIDiff apiDiff) {
// Go through all the ClassDiff objects searching for the above cases.
Iterator iter = apiDiff.packagesChanged.iterator();
while (iter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(iter.next());
Iterator iter2 = pkgDiff.classesChanged.iterator();
while (iter2.hasNext()) {
ClassDiff classDiff = (ClassDiff)(iter2.next());
// Note: using iterators to step through the members gives a
// ConcurrentModificationException exception with large files.
// Constructors
ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
ConstructorAPI removedCtor = ctorArr[ctorIdx];
mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
}
// Methods
MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
MethodAPI removedMethod = methodArr[methodIdx];
// Only merge locally defined methods
if (removedMethod.inheritedFrom_ == null)
mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
}
// Fields
FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
FieldAPI removedField = fieldArr[fieldIdx];
// Only merge locally defined fields
if (removedField.inheritedFrom_ == null)
mergeRemoveAddField(removedField, classDiff, pkgDiff);
}
}
}
}
/**
* Convert some removed and added constructors into changed constructors.
*/
public static void mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff) {
// Search on the type of the constructor
int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
if (startRemoved != -1 && startRemoved == endRemoved &&
startAdded != -1 && startAdded == endAdded) {
// There is only one constructor with the type of the
// removedCtor in both the removed and added constructors.
ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
// Create a MemberDiff for this change
MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
ctorDiff.oldType_ = removedCtor.type_;
ctorDiff.newType_ = addedCtor.type_; // Should be the same as removedCtor.type
ctorDiff.oldExceptions_ = removedCtor.exceptions_;
ctorDiff.newExceptions_ = addedCtor.exceptions_;
ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
// Track changes in documentation
if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
String type = ctorDiff.newType_;
if (type.compareTo("void") == 0)
type = "";
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
String title = link1 + "Class <b>" + classDiff.name_ +
"</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
}
classDiff.ctorsChanged.add(ctorDiff);
// Now remove the entries from the remove and add lists
classDiff.ctorsRemoved.remove(startRemoved);
classDiff.ctorsAdded.remove(startAdded);
if (trace && ctorDiff.modifiersChange_ != null)
System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
}
}
/**
* Convert some removed and added methods into changed methods.
*/
public static void mergeRemoveAddMethod(MethodAPI removedMethod,
ClassDiff classDiff,
PackageDiff pkgDiff) {
mergeSingleMethods(removedMethod, classDiff, pkgDiff);
mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
}
/**
* Convert single removed and added methods into a changed method.
*/
public static void mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
// Search on the name of the method
int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
if (startRemoved != -1 && startRemoved == endRemoved &&
startAdded != -1 && startAdded == endAdded) {
// There is only one method with the name of the
// removedMethod in both the removed and added methods.
MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
if (addedMethod.inheritedFrom_ == null) {
// Create a MemberDiff for this change
MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
methodDiff.oldType_ = removedMethod.returnType_;
methodDiff.newType_ = addedMethod.returnType_;
methodDiff.oldSignature_ = removedMethod.getSignature();
methodDiff.newSignature_ = addedMethod.getSignature();
methodDiff.oldExceptions_ = removedMethod.exceptions_;
methodDiff.newExceptions_ = addedMethod.exceptions_;
// The addModifiersChange field may not have been
// initialized yet if there were multiple methods of the same
// name.
diffMethods(methodDiff, removedMethod, addedMethod);
methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
// Track changes in documentation
if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
String sig = methodDiff.newSignature_;
if (sig.compareTo("void") == 0)
sig = "";
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
}
classDiff.methodsChanged.add(methodDiff);
// Now remove the entries from the remove and add lists
classDiff.methodsRemoved.remove(startRemoved);
classDiff.methodsAdded.remove(startAdded);
if (trace) {
System.out.println("Merged the removal and addition of method " +
removedMethod.name_ +
" into one change");
}
} //if (addedMethod.inheritedFrom_ == null)
}
}
/**
* Convert multiple removed and added methods into changed methods.
* This handles the case where the methods' signatures are unchanged, but
* something else changed.
*/
public static void mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
// Search on the name and signature of the method
int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
if (startRemoved != -1 && endRemoved != -1 &&
startAdded != -1 && endAdded != -1) {
// Find the index of the current removed method
int removedIdx = -1;
for (int i = startRemoved; i <= endRemoved; i++) {
if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
removedIdx = i;
break;
}
}
if (removedIdx == -1) {
System.out.println("Error: removed method index not found");
System.exit(5);
}
// Find the index of the added method with the same signature, if
// it exists, and make sure it is defined locally.
int addedIdx = -1;
for (int i = startAdded; i <= endAdded; i++) {
MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
if (addedMethod2.inheritedFrom_ == null &&
removedMethod.equalSignatures(addedMethod2))
addedIdx = i;
break;
}
if (addedIdx == -1)
return;
MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
// Create a MemberDiff for this change
MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
methodDiff.oldType_ = removedMethod.returnType_;
methodDiff.newType_ = addedMethod.returnType_;
methodDiff.oldSignature_ = removedMethod.getSignature();
methodDiff.newSignature_ = addedMethod.getSignature();
methodDiff.oldExceptions_ = removedMethod.exceptions_;
methodDiff.newExceptions_ = addedMethod.exceptions_;
// The addModifiersChange field may not have been
// initialized yet if there were multiple methods of the same
// name.
diffMethods(methodDiff, removedMethod, addedMethod);
methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
// Track changes in documentation
if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
String sig = methodDiff.newSignature_;
if (sig.compareTo("void") == 0)
sig = "";
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
}
classDiff.methodsChanged.add(methodDiff);
// Now remove the entries from the remove and add lists
classDiff.methodsRemoved.remove(removedIdx);
classDiff.methodsAdded.remove(addedIdx);
if (trace) {
System.out.println("Merged the removal and addition of method " +
removedMethod.name_ +
" into one change. There were multiple methods of this name.");
}
}
}
/**
* Track changes in methods related to abstract, native, and
* synchronized modifiers here.
*/
public static void diffMethods(MemberDiff methodDiff,
MethodAPI oldMethod,
MethodAPI newMethod) {
// Abstract or not
if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
String changeText = "";
if (oldMethod.isAbstract_)
changeText += "Changed from abstract to non-abstract.";
else
changeText += "Changed from non-abstract to abstract.";
methodDiff.addModifiersChange(changeText);
}
// Native or not
if (Diff.showAllChanges &&
oldMethod.isNative_ != newMethod.isNative_) {
String changeText = "";
if (oldMethod.isNative_)
changeText += "Changed from native to non-native.";
else
changeText += "Changed from non-native to native.";
methodDiff.addModifiersChange(changeText);
}
// Synchronized or not
if (Diff.showAllChanges &&
oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
String changeText = "";
if (oldMethod.isSynchronized_)
changeText += "Changed from synchronized to non-synchronized.";
else
changeText += "Changed from non-synchronized to synchronized.";
methodDiff.addModifiersChange(changeText);
}
}
/**
* Convert some removed and added fields into changed fields.
*/
public static void mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff) {
// Search on the name of the field
int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
int startAdded = classDiff.fieldsAdded.indexOf(removedField);
int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
if (startRemoved != -1 && startRemoved == endRemoved &&
startAdded != -1 && startAdded == endAdded) {
// There is only one field with the name of the
// removedField in both the removed and added fields.
FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
if (addedField.inheritedFrom_ == null) {
// Create a MemberDiff for this change
MemberDiff fieldDiff = new MemberDiff(removedField.name_);
fieldDiff.oldType_ = removedField.type_;
fieldDiff.newType_ = addedField.type_;
fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
// Track changes in documentation
if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
}
classDiff.fieldsChanged.add(fieldDiff);
// Now remove the entries from the remove and add lists
classDiff.fieldsRemoved.remove(startRemoved);
classDiff.fieldsAdded.remove(startAdded);
if (trace) {
System.out.println("Merged the removal and addition of field " +
removedField.name_ +
" into one change");
}
} //if (addedField.inheritedFrom == null)
}
}
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent a method, analogous to MethodDoc in the
* Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this method.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class MethodAPI implements Comparable {
/** Name of the method. */
public String name_ = null;
/** Return type of the method. */
public String returnType_ = null;
/**
* The fully qualified name of the class or interface this method is
* inherited from. If this is null, then the method is defined locally
* in this class or interface.
*/
public String inheritedFrom_ = null;
/**
* The exceptions thrown by this method, being all the exception types
* separated by commas. "no exceptions" if no exceptions are thrown.
*/
public String exceptions_ = "no exceptions";
/** Set if this method is abstract. */
public boolean isAbstract_ = false;
/** Set if this method is native. */
public boolean isNative_ = false;
/** Set if this method is synchronized. */
public boolean isSynchronized_ = false;
/** Modifiers for this class. */
public Modifiers modifiers_;
public List params_; // ParamAPI[]
/** The doc block, default is null. */
public String doc_ = null;
/** Constructor. */
public MethodAPI(String name, String returnType, boolean isAbstract,
boolean isNative, boolean isSynchronized,
Modifiers modifiers) {
name_ = name;
returnType_ = returnType;
isAbstract_ = isAbstract;
isNative_ = isNative;
isSynchronized_ = isSynchronized;
modifiers_ = modifiers;
params_ = new ArrayList(); // ParamAPI[]
}
/** Copy constructor. */
public MethodAPI(MethodAPI m) {
name_ = m.name_;
returnType_ = m.returnType_;
inheritedFrom_ = m.inheritedFrom_;
exceptions_ = m.exceptions_;
isAbstract_ = m.isAbstract_;
isNative_ = m.isNative_;
isSynchronized_ = m.isSynchronized_;
modifiers_ = m.modifiers_; // Note: shallow copy
params_ = m.params_; // Note: shallow copy
doc_ = m.doc_;
signature_ = m.signature_; // Cached
}
/**
* Compare two methods, including the return type, and parameter
* names and types, and modifiers.
*/
public int compareTo(Object o) {
MethodAPI oMethod = (MethodAPI)o;
int comp = name_.compareTo(oMethod.name_);
if (comp != 0)
return comp;
comp = returnType_.compareTo(oMethod.returnType_);
if (comp != 0)
return comp;
if (APIComparator.changedInheritance(inheritedFrom_, oMethod.inheritedFrom_) != 0)
return -1;
if (isAbstract_ != oMethod.isAbstract_) {
return -1;
}
if (Diff.showAllChanges &&
isNative_ != oMethod.isNative_) {
return -1;
}
if (Diff.showAllChanges &&
isSynchronized_ != oMethod.isSynchronized_) {
return -1;
}
comp = exceptions_.compareTo(oMethod.exceptions_);
if (comp != 0)
return comp;
comp = modifiers_.compareTo(oMethod.modifiers_);
if (comp != 0)
return comp;
comp = getSignature().compareTo(oMethod.getSignature());
if (comp != 0)
return comp;
if (APIComparator.docChanged(doc_, oMethod.doc_))
return -1;
return 0;
}
/**
* Tests two methods, using just the method name, used by indexOf().
*/
public boolean equals(Object o) {
if (name_.compareTo(((MethodAPI)o).name_) == 0)
return true;
return false;
}
/**
* Tests two methods for equality, using just the signature.
*/
public boolean equalSignatures(Object o) {
if (getSignature().compareTo(((MethodAPI)o).getSignature()) == 0)
return true;
return false;
}
/** Cached result of getSignature(). */
public String signature_ = null;
/** Return the signature of the method. */
public String getSignature() {
if (signature_ != null)
return signature_;
String res = "";
boolean first = true;
Iterator iter = params_.iterator();
while (iter.hasNext()) {
if (!first)
res += ", ";
ParamAPI param = (ParamAPI)(iter.next());
res += param.toString();
first = false;
}
signature_ = res;
return res;
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Track the various modifiers for a program element.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this set of modifiers.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class Modifiers implements Comparable {
/** Set if the program element is static. */
public boolean isStatic = false;
/** Set if the program element is final. */
public boolean isFinal = false;
/** Set if the program element is deprecated. */
public boolean isDeprecated = false;
/**
* The visibility level; "public", "protected", "package" or
* "private"
*/
public String visibility = null;
/** Default constructor. */
public Modifiers() {
}
/** Compare two Modifiers objects by their contents. */
public int compareTo(Object o) {
Modifiers oModifiers = (Modifiers)o;
if (isStatic != oModifiers.isStatic)
return -1;
if (isFinal != oModifiers.isFinal)
return -1;
if (isDeprecated != oModifiers.isDeprecated)
return -1;
if (visibility != null) {
int comp = visibility.compareTo(oModifiers.visibility);
if (comp != 0)
return comp;
}
return 0;
}
/**
* Generate a String describing the differences between the current
* (old) Modifiers object and a new Modifiers object. The string has
* no leading space, but does end in a period.
*
* @param newModifiers The new Modifiers object.
* @return The description of the differences, null if there is no change.
*/
public String diff(Modifiers newModifiers) {
String res = "";
boolean hasContent = false;
if (isStatic != newModifiers.isStatic) {
res += "Change from ";
if (isStatic)
res += "static to non-static.<br>";
else
res += "non-static to static.<br>";
hasContent = true;
}
if (isFinal != newModifiers.isFinal) {
if (hasContent)
res += " ";
res += "Change from ";
if (isFinal)
res += "final to non-final.<br>";
else
res += "non-final to final.<br>";
hasContent = true;
}
if (!HTMLReportGenerator.incompatibleChangesOnly &&
isDeprecated != newModifiers.isDeprecated) {
if (hasContent)
res += " ";
if (isDeprecated)
res += "Change from deprecated to undeprecated.<br>";
else
res += "<b>Now deprecated</b>.<br>";
hasContent = true;
}
if (visibility != null) {
int comp = visibility.compareTo(newModifiers.visibility);
if (comp != 0) {
if (hasContent)
res += " ";
res += "Change of visibility from " + visibility + " to " +
newModifiers.visibility + ".<br>";
hasContent = true;
}
}
if (res.compareTo("") == 0)
return null;
return res;
}
}
package jdiff;
import java.io.*;
import java.util.*;
import com.sun.javadoc.*;
/**
* Class to handle options for JDiff.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class Options {
/** Default constructor. */
public Options() {
}
/**
* Returns the "length" of a given option. If an option takes no
* arguments, its length is one. If it takes one argument, its
* length is two, and so on. This method is called by Javadoc to
* parse the options it does not recognize. It then calls
* {@link #validOptions} to validate them.
* <blockquote>
* <b>Note:</b><br>
* The options arrive as case-sensitive strings. For options that
* are not case-sensitive, use toLowerCase() on the option string
* before comparing it.
* </blockquote>
*
* @param option a String containing an option
* @return an int telling how many components that option has
*/
public static int optionLength(String option) {
String opt = option.toLowerCase();
// Standard options
if (opt.equals("-authorid")) return 2;
if (opt.equals("-versionid")) return 2;
if (opt.equals("-d")) return 2;
if (opt.equals("-classlist")) return 1;
if (opt.equals("-title")) return 2;
if (opt.equals("-docletid")) return 1;
if (opt.equals("-evident")) return 2;
if (opt.equals("-skippkg")) return 2;
if (opt.equals("-skipclass")) return 2;
if (opt.equals("-execdepth")) return 2;
if (opt.equals("-help")) return 1;
if (opt.equals("-version")) return 1;
if (opt.equals("-package")) return 1;
if (opt.equals("-protected")) return 1;
if (opt.equals("-public")) return 1;
if (opt.equals("-private")) return 1;
if (opt.equals("-sourcepath")) return 2;
// Options to control JDiff
if (opt.equals("-apiname")) return 2;
if (opt.equals("-oldapi")) return 2;
if (opt.equals("-newapi")) return 2;
// Options to control the location of the XML files
if (opt.equals("-apidir")) return 2;
if (opt.equals("-oldapidir")) return 2;
if (opt.equals("-newapidir")) return 2;
// Options for the exclusion level for classes and members
if (opt.equals("-excludeclass")) return 2;
if (opt.equals("-excludemember")) return 2;
if (opt.equals("-firstsentence")) return 1;
if (opt.equals("-docchanges")) return 1;
if (opt.equals("-incompatible")) return 1;
if (opt.equals("-packagesonly")) return 1;
if (opt.equals("-showallchanges")) return 1;
// Option to change the location for the existing Javadoc
// documentation for the new API. Default is "../"
if (opt.equals("-javadocnew")) return 2;
// Option to change the location for the existing Javadoc
// documentation for the old API. Default is null.
if (opt.equals("-javadocold")) return 2;
if (opt.equals("-baseuri")) return 2;
// Option not to suggest comments at all
if (opt.equals("-nosuggest")) return 2;
// Option to enable checking that the comments end with a period.
if (opt.equals("-checkcomments")) return 1;
// Option to retain non-printing characters in comments.
if (opt.equals("-retainnonprinting")) return 1;
// Option for the name of the exclude tag
if (opt.equals("-excludetag")) return 2;
// Generate statistical output
if (opt.equals("-stats")) return 1;
// Set the browser window title
if (opt.equals("-windowtitle")) return 2;
// Set the report title
if (opt.equals("-doctitle")) return 2;
// Tells JDiff we are running in 'script mode' so it must
// return a specific return code based on the comparison
// of the source files
if (opt.equals("-script")) return 1;
return 0;
}//optionLength()
/**
* After parsing the available options using {@link #optionLength},
* Javadoc invokes this method with an array of options-arrays, where
* the first item in any array is the option, and subsequent items in
* that array are its arguments. So, if -print is an option that takes
* no arguments, and -copies is an option that takes 1 argument, then
* <pre>
* -print -copies 3
* </pre>
* produces an array of arrays that looks like:
* <pre>
* option[0][0] = -print
* option[1][0] = -copies
* option[1][1] = 3
* </pre>
* (By convention, command line switches start with a "-", but
* they don't have to.)
* <p>
* <b>Note:</b><br>
* Javadoc passes <i>all</i>parameters to this method, not just
* those that Javadoc doesn't recognize. The only way to
* identify unexpected arguments is therefore to check for every
* Javadoc parameter as well as doclet parameters.
*
* @param options an array of String arrays, one per option
* @param reporter a DocErrorReporter for generating error messages
* @return true if no errors were found, and all options are
* valid
*/
public static boolean validOptions(String[][] options,
DocErrorReporter reporter) {
final DocErrorReporter errOut = reporter;
// A nice object-oriented way of handling errors. An instance of this
// class puts out an error message and keeps track of whether or not
// an error was found.
class ErrorHandler {
boolean noErrorsFound = true;
void msg(String msg) {
noErrorsFound = false;
errOut.printError(msg);
}
}
ErrorHandler err = new ErrorHandler();
if (trace)
System.out.println("Command line arguments: ");
for (int i = 0; i < options.length; i++) {
for (int j = 0; j < options[i].length; j++) {
Options.cmdOptions += " " + options[i][j];
if (trace)
System.out.print(" " + options[i][j]);
}
}
if (trace)
System.out.println();
for (int i = 0; i < options.length; i++) {
if (options[i][0].toLowerCase().equals("-apiname")) {
if (options[i].length < 2) {
err.msg("No version identifier specified after -apiname option.");
} else if (JDiff.compareAPIs) {
err.msg("Use the -apiname option, or the -oldapi and -newapi options, but not both.");
} else {
String filename = options[i][1];
RootDocToXML.apiIdentifier = filename;
filename = filename.replace(' ', '_');
RootDocToXML.outputFileName = filename + ".xml";
JDiff.writeXML = true;
JDiff.compareAPIs = false;
}
continue;
}
if (options[i][0].toLowerCase().equals("-apidir")) {
if (options[i].length < 2) {
err.msg("No directory specified after -apidir option.");
} else {
RootDocToXML.outputDirectory = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-oldapi")) {
if (options[i].length < 2) {
err.msg("No version identifier specified after -oldapi option.");
} else if (JDiff.writeXML) {
err.msg("Use the -apiname or -oldapi option, but not both.");
} else {
String filename = options[i][1];
filename = filename.replace(' ', '_');
JDiff.oldFileName = filename + ".xml";
JDiff.writeXML = false;
JDiff.compareAPIs = true;
}
continue;
}
if (options[i][0].toLowerCase().equals("-oldapidir")) {
if (options[i].length < 2) {
err.msg("No directory specified after -oldapidir option.");
} else {
JDiff.oldDirectory = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-newapi")) {
if (options[i].length < 2) {
err.msg("No version identifier specified after -newapi option.");
} else if (JDiff.writeXML) {
err.msg("Use the -apiname or -newapi option, but not both.");
} else {
String filename = options[i][1];
filename = filename.replace(' ', '_');
JDiff.newFileName = filename + ".xml";
JDiff.writeXML = false;
JDiff.compareAPIs = true;
}
continue;
}
if (options[i][0].toLowerCase().equals("-newapidir")) {
if (options[i].length < 2) {
err.msg("No directory specified after -newapidir option.");
} else {
JDiff.newDirectory = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-d")) {
if (options[i].length < 2) {
err.msg("No directory specified after -d option.");
} else {
HTMLReportGenerator.outputDir = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-javadocnew")) {
if (options[i].length < 2) {
err.msg("No location specified after -javadocnew option.");
} else {
HTMLReportGenerator.newDocPrefix = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-javadocold")) {
if (options[i].length < 2) {
err.msg("No location specified after -javadocold option.");
} else {
HTMLReportGenerator.oldDocPrefix = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-baseuri")) {
if (options[i].length < 2) {
err.msg("No base location specified after -baseURI option.");
} else {
RootDocToXML.baseURI = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-excludeclass")) {
if (options[i].length < 2) {
err.msg("No level (public|protected|package|private) specified after -excludeclass option.");
} else {
String level = options[i][1];
if (level.compareTo("public") != 0 &&
level.compareTo("protected") != 0 &&
level.compareTo("package") != 0 &&
level.compareTo("private") != 0) {
err.msg("Level specified after -excludeclass option must be one of (public|protected|package|private).");
} else {
RootDocToXML.classVisibilityLevel = level;
}
}
continue;
}
if (options[i][0].toLowerCase().equals("-excludemember")) {
if (options[i].length < 2) {
err.msg("No level (public|protected|package|private) specified after -excludemember option.");
} else {
String level = options[i][1];
if (level.compareTo("public") != 0 &&
level.compareTo("protected") != 0 &&
level.compareTo("package") != 0 &&
level.compareTo("private") != 0) {
err.msg("Level specified after -excludemember option must be one of (public|protected|package|private).");
} else {
RootDocToXML.memberVisibilityLevel = level;
}
}
continue;
}
if (options[i][0].toLowerCase().equals("-firstsentence")) {
RootDocToXML.saveAllDocs = false;
continue;
}
if (options[i][0].toLowerCase().equals("-docchanges")) {
HTMLReportGenerator.reportDocChanges = true;
Diff.noDocDiffs = false;
continue;
}
if (options[i][0].toLowerCase().equals("-incompatible")) {
HTMLReportGenerator.incompatibleChangesOnly = true;
continue;
}
if (options[i][0].toLowerCase().equals("-packagesonly")) {
RootDocToXML.packagesOnly = true;
continue;
}
if (options[i][0].toLowerCase().equals("-showallchanges")) {
Diff.showAllChanges = true;
continue;
}
if (options[i][0].toLowerCase().equals("-nosuggest")) {
if (options[i].length < 2) {
err.msg("No level (all|remove|add|change) specified after -nosuggest option.");
} else {
String level = options[i][1];
if (level.compareTo("all") != 0 &&
level.compareTo("remove") != 0 &&
level.compareTo("add") != 0 &&
level.compareTo("change") != 0) {
err.msg("Level specified after -nosuggest option must be one of (all|remove|add|change).");
} else {
if (level.compareTo("removal") == 0)
HTMLReportGenerator.noCommentsOnRemovals = true;
else if (level.compareTo("add") == 0)
HTMLReportGenerator.noCommentsOnAdditions = true;
else if (level.compareTo("change") == 0)
HTMLReportGenerator.noCommentsOnChanges = true;
else if (level.compareTo("all") == 0) {
HTMLReportGenerator.noCommentsOnRemovals = true;
HTMLReportGenerator.noCommentsOnAdditions = true;
HTMLReportGenerator.noCommentsOnChanges = true;
}
}
}
continue;
}
if (options[i][0].toLowerCase().equals("-checkcomments")) {
APIHandler.checkIsSentence = true;
continue;
}
if (options[i][0].toLowerCase().equals("-retainnonprinting")) {
RootDocToXML.stripNonPrintables = false;
continue;
}
if (options[i][0].toLowerCase().equals("-excludetag")) {
if (options[i].length < 2) {
err.msg("No exclude tag specified after -excludetag option.");
} else {
RootDocToXML.excludeTag = options[i][1];
RootDocToXML.excludeTag = RootDocToXML.excludeTag.trim();
RootDocToXML.doExclude = true;
}
continue;
}
if (options[i][0].toLowerCase().equals("-stats")) {
HTMLReportGenerator.doStats = true;
continue;
}
if (options[i][0].toLowerCase().equals("-doctitle")) {
if (options[i].length < 2) {
err.msg("No HTML text specified after -doctitle option.");
} else {
HTMLReportGenerator.docTitle = options[i][1];
}
continue;
}
if (options[i][0].toLowerCase().equals("-windowtitle")) {
if (options[i].length < 2) {
err.msg("No text specified after -windowtitle option.");
} else {
HTMLReportGenerator.windowTitle = options[i][1];
}
continue;
}
if(options[i][0].toLowerCase().equals("-script")) {
JDiff.runningScript = true;
continue;
}
if (options[i][0].toLowerCase().equals("-version")) {
System.out.println("JDiff version: " + JDiff.version);
System.exit(0);
}
if (options[i][0].toLowerCase().equals("-help")) {
usage();
System.exit(0);
}
}//for
if (!JDiff.writeXML && !JDiff.compareAPIs) {
err.msg("First use the -apiname option to generate an XML file for one API.");
err.msg("Then use the -apiname option again to generate another XML file for a different version of the API.");
err.msg("Finally use the -oldapi option and -newapi option to generate a report about how the APIs differ.");
}
return err.noErrorsFound;
}// validOptions()
/** Display the arguments for JDiff. */
public static void usage() {
System.err.println("JDiff version: " + JDiff.version);
System.err.println("");
System.err.println("Valid JDiff arguments:");
System.err.println("");
System.err.println(" -apiname <Name of a version>");
System.err.println(" -oldapi <Name of a version>");
System.err.println(" -newapi <Name of a version>");
System.err.println(" Optional Arguments");
System.err.println();
System.err.println(" -d <directory> Destination directory for output HTML files");
System.err.println(" -apidir <directory> Destination directory for the XML file generated with the '-apiname' argument.");
System.err.println(" -oldapidir <directory> Location of the XML file for the old API");
System.err.println(" -newapidir <directory> Location of the XML file for the new API");
System.err.println(" -sourcepath <location of Java source files>");
System.err.println(" -javadocnew <location of existing Javadoc files for the new API>");
System.err.println(" -javadocold <location of existing Javadoc files for the old API>");
System.err.println(" -baseURI <base> Use \"base\" as the base location of the various DTDs and Schemas used by JDiff");
System.err.println(" -excludeclass [public|protected|package|private] Exclude classes which are not public, protected etc");
System.err.println(" -excludemember [public|protected|package|private] Exclude members which are not public, protected etc");
System.err.println(" -firstsentence Save only the first sentence of each comment block with the API.");
System.err.println(" -docchanges Report changes in Javadoc comments between the APIs");
System.err.println(" -incompatible Only report incompatible changes");
System.err.println(" -nosuggest [all|remove|add|change] Do not add suggested comments to all, or the removed, added or chabged sections");
System.err.println(" -checkcomments Check that comments are sentences");
System.err.println(" -stripnonprinting Remove non-printable characters from comments.");
System.err.println(" -excludetag <tag> Define the Javadoc tag which implies exclusion");
System.err.println(" -stats Generate statistical output");
System.err.println(" -help (generates this output)");
System.err.println("");
System.err.println("For more help, see jdiff.html");
}
/** All the options passed on the command line. Logged to XML. */
public static String cmdOptions = "";
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent a package, analogous to PackageDoc in the
* Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this package.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class PackageAPI implements Comparable {
/** Full qualified name of the package. */
public String name_;
/** Classes within this package. */
public List classes_; // ClassAPI[]
/** The doc block, default is null. */
public String doc_ = null;
/** Constructor. */
public PackageAPI(String name) {
name_ = name;
classes_ = new ArrayList(); // ClassAPI[]
}
/** Compare two PackageAPI objects by name. */
public int compareTo(Object o) {
PackageAPI oPackageAPI = (PackageAPI)o;
if (APIComparator.docChanged(doc_, oPackageAPI.doc_))
return -1;
return name_.compareTo(oPackageAPI.name_);
}
/**
* Tests two packages, using just the package name, used by indexOf().
*/
public boolean equals(Object o) {
if (name_.compareTo(((PackageAPI)o).name_) == 0)
return true;
return false;
}
}
package jdiff;
import java.util.*;
import com.sun.javadoc.*;
/**
* Changes between two packages.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class PackageDiff {
public String name_;
/** Classes added in the new API. */
public List classesAdded = null;
/** Classes removed in the new API. */
public List classesRemoved = null;
/** Classes changed in the new API. */
public List classesChanged = null;
/**
* A string describing the changes in documentation.
*/
public String documentationChange_ = null;
/* The percentage difference for this package. */
public double pdiff = 0.0;
/** Default constructor. */
public PackageDiff(String name) {
name_ = name;
classesAdded = new ArrayList(); // ClassAPI[]
classesRemoved = new ArrayList(); // ClassAPI[]
classesChanged = new ArrayList(); // ClassDiff[]
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent any (name, type) pair such as a parameter.
* Analogous to ParamType in the Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this parameter.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class ParamAPI implements Comparable {
/** Name of the (name, type) pair. */
public String name_;
/** Type of the (name, type) pair. */
public String type_;
public ParamAPI(String name, String type) {
name_ = name;
type_ = type;
}
/** Compare two ParamAPI objects using both name and type. */
public int compareTo(Object o) {
ParamAPI oParamAPI = (ParamAPI)o;
int comp = name_.compareTo(oParamAPI.name_);
if (comp != 0)
return comp;
comp = type_.compareTo(oParamAPI.type_);
if (comp != 0)
return comp;
return 0;
}
/**
* Tests two ParamAPI objects using just the name, used by indexOf().
*/
public boolean equals(Object o) {
if (name_.compareTo(((ParamAPI)o).name_) == 0)
return true;
return false;
}
/** Used to create signatures. */
public String toString() {
if (type_.compareTo("void") == 0)
return "";
return type_;
}
}
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("&", "&amp;").
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
package jdiff;
import java.util.*;
/**
* Emit a standard text report with only the names
* of all packages which need a major version number change.
*/
public class ScriptReport {
/** Default constructor. */
public ScriptReport() { }
/**
* Checks to see if the tested module is backwards compatible.
*
* @return 100 if no changes
* 101 if compatible changes
* 102 if not compatible
*/
public int run(APIComparator comp) {
// Get the APIDiff
APIDiff apiDiff = comp.apiDiff;
if(apiDiff.packagesRemoved.size() > 0) {
return 102;
}
Iterator piter = apiDiff.packagesChanged.iterator();
while (piter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(piter.next());
if(pkgDiff.classesRemoved.size() > 0) {
return 102;
}
Iterator citer = pkgDiff.classesChanged.iterator();
while(citer.hasNext()) {
ClassDiff classDiff = (ClassDiff)(citer.next());
if(classDiff.methodsRemoved.size() > 0) {
return 102;
}
Iterator miter = classDiff.methodsChanged.iterator();
while (miter.hasNext()) {
// Check if method has different return type
MemberDiff memberDiff = (MemberDiff)(miter.next());
if(!memberDiff.oldType_ .equals(memberDiff.newType_)) {
return 102;
}
}
}
}
// If there were any changes, but we haven't returned yet
// they must all be backwards compatible changes
if(apiDiff.packagesChanged.size() > 0) {
return 101;
}
// If we've reached here there must be no changes at all
return 100;
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Represents a single comment element. Has an identifier and some text.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class SingleComment implements Comparable {
/** The identifier for this comment. */
public String id_ = null;
/** The text of this comment. */
public String text_ = null;
/** If false, then this comment is inactive. */
public boolean isUsed_ = true;
public SingleComment(String id, String text) {
// Escape the commentID in case it contains "<" or ">"
// characters (generics)
id_ = id.replaceAll("<", "&lt;").replaceAll(">", "&gt;");;
text_ = text;
}
/** Compare two SingleComment objects using just the id. */
public int compareTo(Object o) {
return id_.compareTo(((SingleComment)o).id_);
}
}
package jdiff;
import java.util.*;
import java.io.*;
/**
* Reads in lines from an input stream and displays them.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com.
*/
class StreamReader extends Thread {
/** The input stream. */
InputStream is_;
/** Constructor which takes an InputStream. */
StreamReader(InputStream is) {
is_ = is;
}
/** Method which is called when this thread is started. */
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is_);
BufferedReader br = new BufferedReader(isr);
String line = null;
while((line = br.readLine()) != null)
System.out.println(line);
} catch (IOException ioe) {
System.out.println("IO Error invoking Javadoc");
ioe.printStackTrace();
} catch (Exception e) {
// Ignore read errors which indicate that the process is complete
}
}
}
package jdiff;
import java.io.*;
import java.util.*;
/* For SAX parsing in APIHandler */
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.*;
/**
* Creates an API object from an XML file. The API object is the internal
* representation of an API.
* All methods in this class for populating an API object are static.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class XMLToAPI {
/** The instance of the API object which is populated from the file. */
private static API api_ = null;
/** Default constructor. */
private XMLToAPI() {
}
/**
* Read the file where the XML representing the API is stored.
*
* @param filename The full name of the file containing the XML
* representing the API
* @param createGlobalComments If set, then store possible comments
* @param apiName The simple name of the API file. If -oldapidir and
* -newapidir are not used, then this is the same as
* the filename parameter
*/
public static API readFile(String filename, boolean createGlobalComments,
String apiName) {
// The instance of the API object which is populated from the file.
api_ = new API();
api_.name_ = apiName; // Checked later
try {
XMLReader parser = null;
DefaultHandler handler = new APIHandler(api_, createGlobalComments);
try {
String parserName = System.getProperty("org.xml.sax.driver");
if (parserName == null) {
parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
} else {
// Let the underlying mechanisms try to work out which
// class to instantiate
parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
}
} catch (SAXException saxe) {
System.out.println("SAXException: " + saxe);
saxe.printStackTrace();
System.exit(1);
}
if (validateXML) {
parser.setFeature("http://xml.org/sax/features/namespaces", true);
parser.setFeature("http://xml.org/sax/features/validation", true);
parser.setFeature("http://apache.org/xml/features/validation/schema", true);
}
parser.setContentHandler(handler);
parser.setErrorHandler(handler);
parser.parse(new InputSource(new FileInputStream(new File(filename))));
} catch(org.xml.sax.SAXNotRecognizedException snre) {
System.out.println("SAX Parser does not recognize feature: " + snre);
snre.printStackTrace();
System.exit(1);
} catch(org.xml.sax.SAXNotSupportedException snse) {
System.out.println("SAX Parser feature is not supported: " + snse);
snse.printStackTrace();
System.exit(1);
} catch(org.xml.sax.SAXException saxe) {
System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
saxe.printStackTrace();
System.exit(1);
} catch(java.io.IOException ioe) {
System.out.println("IOException parsing file '" + filename + "' : " + ioe);
ioe.printStackTrace();
System.exit(1);
}
// Add the inherited methods and fields to each class
addInheritedElements();
return api_;
} //readFile()
/**
* Add the inherited methods and fields to each class in turn.
*/
public static void addInheritedElements() {
Iterator iter = api_.packages_.iterator();
while (iter.hasNext()) {
PackageAPI pkg = (PackageAPI)(iter.next());
Iterator iter2 = pkg.classes_.iterator();
while (iter2.hasNext()) {
ClassAPI cls = (ClassAPI)(iter2.next());
// Look up any inherited classes or interfaces
if (cls.extends_ != null) {
ClassAPI parent = (ClassAPI)api_.classes_.get(cls.extends_);
if (parent != null)
addInheritedElements(cls, parent, cls.extends_);
}
if (cls.implements_.size() != 0) {
Iterator iter3 = cls.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent = (ClassAPI)api_.classes_.get(implName);
if (parent != null)
addInheritedElements(cls, parent, implName);
}
}
} //while (iter2.hasNext())
} //while (iter.hasNext())
}
/**
* Add all the inherited methods and fields in the second class to
* the first class, marking them as inherited from the second class.
* Do not add a method or a field if it is already defined locally.
*
* Only elements at the specified visibility level or
* higher appear in the XML file. All that remains to be tested for
* a private element, which is never inherited.
*
* If the parent class inherits any classes or interfaces, call this
* method recursively with those parents.
*/
public static void addInheritedElements(ClassAPI child, ClassAPI parent,
String fqParentName) {
if (parent.methods_.size() != 0) {
Iterator iter = parent.methods_.iterator();
while (iter.hasNext()) {
MethodAPI m = (MethodAPI)(iter.next());
// See if it the method is overridden locally
boolean overridden = false;
Iterator iter2 = child.methods_.iterator();
while (iter2.hasNext()) {
MethodAPI localM = (MethodAPI)(iter2.next());
if (localM.name_.compareTo(m.name_) == 0 &&
localM.getSignature().compareTo(m.getSignature()) == 0)
overridden = true;
}
if (!overridden && m.inheritedFrom_ == null &&
m.modifiers_.visibility != null &&
m.modifiers_.visibility.compareTo("private") != 0) {
MethodAPI m2 = new MethodAPI(m);
m2.inheritedFrom_ = fqParentName;
child.methods_.add(m2);
}
}
}
if (parent.fields_.size() != 0) {
Iterator iter = parent.fields_.iterator();
while (iter.hasNext()) {
FieldAPI f = (FieldAPI)(iter.next());
if (child.fields_.indexOf(f) == -1 &&
f.inheritedFrom_ == null &&
f.modifiers_.visibility != null &&
f.modifiers_.visibility.compareTo("private") != 0) {
FieldAPI f2 = new FieldAPI(f);
f2.inheritedFrom_ = fqParentName;
child.fields_.add(f2);
}
}
}
// Look up any inherited classes or interfaces
if (parent.extends_ != null) {
ClassAPI parent2 = (ClassAPI)api_.classes_.get(parent.extends_);
if (parent2 != null)
addInheritedElements(child, parent2, parent.extends_);
}
if (parent.implements_.size() != 0) {
Iterator iter3 = parent.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent2 = (ClassAPI)api_.classes_.get(implName);
if (parent2 != null)
addInheritedElements(child, parent2, implName);
}
}
}
//
// Methods to add data to an API object. Called by the XML parser.
//
/**
* Set the name of the API object.
*
* @param name The name of the package.
*/
public static void nameAPI(String name) {
if (name == null) {
System.out.println("Error: no API identifier found in the XML file '" + api_.name_ + "'");
System.exit(3);
}
// Check the given name against the filename currently stored in
// the name_ field
String filename2 = name.replace(' ','_');
filename2 += ".xml";
if (filename2.compareTo(api_.name_) != 0) {
System.out.println("Warning: API identifier in the XML file (" +
name + ") differs from the name of the file '" +
api_.name_ + "'");
}
api_.name_ = name;
}
/**
* Create a new package and add it to the API. Called by the XML parser.
*
* @param name The name of the package.
*/
public static void addPackage(String name) {
api_.currPkg_ = new PackageAPI(name);
api_.packages_.add(api_.currPkg_);
}
/**
* Create a new class and add it to the current package. Called by the XML parser.
*
* @param name The name of the class.
* @param parent The name of the parent class, null if no class is extended.
* @param modifiers Modifiers for this class.
*/
public static void addClass(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, false, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
String fqName = api_.currPkg_.name_ + "." + name;
ClassAPI caOld = (ClassAPI)api_.classes_.put(fqName, api_.currClass_);
if (caOld != null) {
System.out.println("Warning: duplicate class : " + fqName + " found. Using the first instance only.");
}
}
/**
* Add an new interface and add it to the current package. Called by the
* XML parser.
*
* @param name The name of the interface.
* @param parent The name of the parent interface, null if no
* interface is extended.
*/
public static void addInterface(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, true, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
}
/**
* Add an inherited interface to the current class. Called by the XML
* parser.
*
* @param name The name of the inherited interface.
*/
public static void addImplements(String name) {
api_.currClass_.implements_.add(name);
}
/**
* Add a constructor to the current class. Called by the XML parser.
*
* @param name The name of the constructor.
* @param type The type of the constructor.
* @param modifiers Modifiers for this constructor.
*/
public static void addCtor(String type, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currCtor_ = new ConstructorAPI(t, modifiers);
api_.currClass_.ctors_.add(api_.currCtor_);
}
/**
* Add a method to the current class. Called by the XML parser.
*
* @param name The name of the method.
* @param returnType The return type of the method, null if it is void.
* @param modifiers Modifiers for this method.
*/
public static void addMethod(String name, String returnType,
boolean isAbstract, boolean isNative,
boolean isSynchronized, Modifiers modifiers) {
String rt = returnType;
if (rt == null)
rt = "void";
api_.currMethod_ = new MethodAPI(name, rt, isAbstract, isNative,
isSynchronized, modifiers);
api_.currClass_.methods_.add(api_.currMethod_);
}
/**
* Add a field to the current class. Called by the XML parser.
*
* @param name The name of the field.
* @param type The type of the field, null if it is void.
* @param modifiers Modifiers for this field.
*/
public static void addField(String name, String type, boolean isTransient,
boolean isVolatile, String value, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currField_ = new FieldAPI(name, t, isTransient, isVolatile, value, modifiers);
api_.currClass_.fields_.add(api_.currField_);
}
/**
* Add a parameter to the current method. Called by the XML parser.
* Constuctors have their type (signature) in an attribute, since it
* is often shorter and makes parsing a little easier.
*
* @param name The name of the parameter.
* @param type The type of the parameter, null if it is void.
*/
public static void addParam(String name, String type) {
String t = type;
if (t == null)
t = "void";
ParamAPI paramAPI = new ParamAPI(name, t);
api_.currMethod_.params_.add(paramAPI);
}
/**
* Add an exception to the current method or constructor.
* Called by the XML parser.
*
* @param name The name of the parameter.
* @param type The type of the parameter.
* May be null in JDiff1.0.8 and earlier versions.
* @param currElement Name of the current element.
*/
public static void addException(String name, String type, String currElement) {
String exceptionId = type;
if (type == null || !showExceptionTypes)
exceptionId = name;
if (currElement.compareTo("method") == 0) {
if (api_.currMethod_.exceptions_.compareTo("no exceptions") == 0)
api_.currMethod_.exceptions_ = exceptionId;
else
api_.currMethod_.exceptions_ += ", " + exceptionId;
} else {
if (api_.currCtor_.exceptions_.compareTo("no exceptions") == 0)
api_.currCtor_.exceptions_ = exceptionId;
else
api_.currCtor_.exceptions_ += ", " + exceptionId;
}
}
/**
* If set, validate the XML which represents an API. By default, this is
* not set for reasons of efficiency, and also because if JDiff generated
* the XML, it should not need validating.
*/
public static boolean validateXML = false;
/**
* If set, then store and display the whole qualified name of exceptions.
* If not set, then store and display just the name of the exception,
* which is shorter, but may not detect when an exception changes class,
* but retains the same name.
*/
private static boolean showExceptionTypes = true;
}