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 4574 deletions
thirdparty/jdiff/v-custom/lib/jdiff_logo.gif

1.82 KiB

thirdparty/jdiff/v-custom/lib/new.gif

116 B

File deleted
<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation>
Schema for JDiff API representation.
</xsd:documentation>
</xsd:annotation>
<xsd:element name="api" type="apiType"/>
<xsd:complexType name="apiType">
<xsd:sequence>
<xsd:element name="package" type="packageType" minOccurs='1' maxOccurs='unbounded'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="jdversion" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="packageType">
<xsd:sequence>
<xsd:choice maxOccurs='unbounded'>
<xsd:element name="class" type="classType"/>
<xsd:element name="interface" type="classType"/>
</xsd:choice>
<xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="classType">
<xsd:sequence>
<xsd:element name="implements" type="interfaceTypeName" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="constructor" type="constructorType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="method" type="methodType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="field" type="fieldType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="extends" type="xsd:string" use='optional'/>
<xsd:attribute name="abstract" type="xsd:boolean"/>
<xsd:attribute name="src" type="xsd:string" use='optional'/>
<xsd:attribute name="static" type="xsd:boolean"/>
<xsd:attribute name="final" type="xsd:boolean"/>
<xsd:attribute name="deprecated" type="xsd:string"/>
<xsd:attribute name="visibility" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="interfaceTypeName">
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="constructorType">
<xsd:sequence>
<xsd:element name="exception" type="exceptionType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string" use='optional'/>
<xsd:attribute name="src" type="xsd:string" use='optional'/>
<xsd:attribute name="static" type="xsd:boolean"/>
<xsd:attribute name="final" type="xsd:boolean"/>
<xsd:attribute name="deprecated" type="xsd:string"/>
<xsd:attribute name="visibility" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="paramsType">
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="exceptionType">
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="methodType">
<xsd:sequence>
<xsd:element name="param" type="paramsType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="exception" type="exceptionType" minOccurs='0' maxOccurs='unbounded'/>
<xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="return" type="xsd:string" use='optional'/>
<xsd:attribute name="abstract" type="xsd:boolean"/>
<xsd:attribute name="native" type="xsd:boolean"/>
<xsd:attribute name="synchronized" type="xsd:boolean"/>
<xsd:attribute name="src" type="xsd:string" use='optional'/>
<xsd:attribute name="static" type="xsd:boolean"/>
<xsd:attribute name="final" type="xsd:boolean"/>
<xsd:attribute name="deprecated" type="xsd:string"/>
<xsd:attribute name="visibility" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="fieldType">
<xsd:sequence>
<xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="transient" type="xsd:boolean"/>
<xsd:attribute name="volatile" type="xsd:boolean"/>
<xsd:attribute name="value" type="xsd:string" use='optional'/>
<xsd:attribute name="src" type="xsd:string" use='optional'/>
<xsd:attribute name="static" type="xsd:boolean"/>
<xsd:attribute name="final" type="xsd:boolean"/>
<xsd:attribute name="deprecated" type="xsd:string"/>
<xsd:attribute name="visibility" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation>
Schema for JDiff comments.
</xsd:documentation>
</xsd:annotation>
<xsd:element name="comments" type="commentsType"/>
<xsd:complexType name="commentsType">
<xsd:sequence>
<xsd:element name="comment" type="commentType" minOccurs='0' maxOccurs='unbounded'/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="jdversion" type="xsd:string"/>
</xsd:complexType>
<xsd:complexType name="commentType">
<xsd:sequence>
<xsd:element name="identifier" type="identifierType" minOccurs='1' maxOccurs='unbounded'/>
<xsd:element name="text" type="xsd:string" minOccurs='1' maxOccurs='1'/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="identifierType">
<xsd:attribute name="id" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>
\ No newline at end of file
*.class
package jdiff;
import java.io.*;
import java.util.*;
/**
* The internal representation of an API.
*
* RootDoc could have been used for representing this, but
* you cannot serialize a RootDoc object - see
* http://developer.java.sun.com/developer/bugParade/bugs/4125581.html
* You might be able use Javadoc.Main() to create another RootDoc, but the
* methods are package private. You can run javadoc in J2SE1.4, see:
* http://java.sun.com/j2se/1.4/docs/tooldocs/javadoc/standard-doclet.html#runningprogrammatically
* but you still can't get the RootDoc object.
*
* The advantage of writing out an XML representation of each API is that
* later runs of JDiff don't have to have Javadoc scan all the files again,
* a possibly lengthy process. XML also permits other source code in
* languages other than Java to be scanned to produce XML, and then versions
* of JDiff can be used to create documents describing the difference in those
* APIs.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class API {
/**
* The list of all the top-level packages.
* Each package contains classes, each class contains members, and so on.
*/
public List packages_; // PackageAPI[]
/**
* The list of all the classes.
* This is used to generate the methods and fields which are inherited,
* rather than storing them in the XML file.
*/
public Hashtable classes_;
/**
* The String which identifies this API, e.g. &quotSuperProduct 1.3&quot;.
*/
public String name_ = null;
/** The current package being added to during parsing. */
public PackageAPI currPkg_ = null;
/** The current class being added to during parsing. */
public ClassAPI currClass_ = null;
/** The current constructor being added to during parsing. */
public ConstructorAPI currCtor_ = null;
/** The current method being added to during parsing. */
public MethodAPI currMethod_ = null;
/** The current field being added to during parsing. */
public FieldAPI currField_ = null;
/** Default constructor. */
public API() {
packages_ = new ArrayList(); //PackageAPI[]
classes_ = new Hashtable(); //ClassAPI
}
//
// Methods to display the contents of an API object.
//
/** Amount by which to increment each indentation. */
public static final int indentInc = 2;
/** Display the contents of the API object. */
public void dump() {
int indent = 0;
Iterator iter = packages_.iterator();
while (iter.hasNext()) {
dumpPackage((PackageAPI)(iter.next()), indent);
}
}
/**
* Display the contents of a PackageAPI object.
*
* @param pkg The given PackageAPI object.
* @param indent The number of spaces to indent the output.
*/
public void dumpPackage(PackageAPI pkg, int indent) {
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Package Name: " + pkg.name_);
Iterator iter = pkg.classes_.iterator();
while (iter.hasNext()) {
dumpClass((ClassAPI)(iter.next()), indent + indentInc);
}
// Display documentation
if (pkg.doc_ != null) {
System.out.print("Package doc block:");
System.out.println("\"" + pkg.doc_ + "\"");
}
}
/**
* Display the contents of a ClassAPI object.
*
* @param c The given ClassAPI object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpClass(ClassAPI c, int indent) {
for (int i = 0; i < indent; i++) System.out.print(" ");
if (c.isInterface_)
System.out.println("Interface name: " + c.name_);
else
System.out.println("Class Name: " + c.name_);
if (c.extends_ != null) {
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Extends: " + c.extends_);
}
if (c.implements_.size() != 0) {
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Implements: ");
Iterator iter = c.implements_.iterator();
while (iter.hasNext()) {
String interfaceImpl = (String)(iter.next());
for (int i = 0; i < indent + 2; i++) System.out.print(" ");
System.out.println(" " + interfaceImpl);
}
}
// Dump modifiers specific to a class
if (c.isAbstract_)
System.out.print("abstract ");
// Dump modifiers common to all
dumpModifiers(c.modifiers_, indent);
// Dump ctors
Iterator iter = c.ctors_.iterator();
while (iter.hasNext()) {
dumpCtor((ConstructorAPI)(iter.next()), indent + indentInc);
}
// Dump methods
iter = c.methods_.iterator();
while (iter.hasNext()) {
dumpMethod((MethodAPI)(iter.next()), indent + indentInc);
}
// Dump fields
iter = c.fields_.iterator();
while (iter.hasNext()) {
dumpField((FieldAPI)(iter.next()), indent + indentInc);
}
// Display documentation
if (c.doc_ != null) {
System.out.print("Class doc block:");
System.out.println("\"" + c.doc_ + "\"");
} else
System.out.println();
}
/**
* Display the contents of the Modifiers object.
*
* @param c The given Modifiers object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpModifiers(Modifiers m, int indent) {
for (int i = 0; i < indent; i++) System.out.print(" ");
if (m.isStatic)
System.out.print("static ");
if (m.isFinal)
System.out.print("final ");
if (m.visibility != null)
System.out.print("visibility = " + m.visibility + " ");
// Flush the line
System.out.println();
}
/**
* Display the contents of a constructor.
*
* @param c The given constructor object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpCtor(ConstructorAPI c, int indent) {
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Ctor type: " + c.type_);
// Display exceptions
System.out.print("exceptions: " + c.exceptions_ + " ");
// Dump modifiers common to all
dumpModifiers(c.modifiers_, indent);
// Display documentation
if (c.doc_ != null) {
System.out.print("Ctor doc block:");
System.out.println("\"" + c.doc_ + "\"");
}
}
/**
* Display the contents of a MethodAPI object.
*
* @param m The given MethodAPI object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpMethod(MethodAPI m, int indent) {
if (m.inheritedFrom_ != null)
return;
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.print("Method Name: " + m.name_);
if (m.inheritedFrom_ != null)
System.out.println(", inherited from: " + m.inheritedFrom_);
if (m.returnType_ != null)
System.out.println(", return type: " + m.returnType_);
else
System.out.println();
// Dump modifiers specific to a method
if (m.isAbstract_)
System.out.print("abstract ");
if (m.isNative_)
System.out.print("native ");
if (m.isSynchronized_)
System.out.print("synchronized ");
// Display exceptions
System.out.print("exceptions: " + m.exceptions_ + " ");
// Dump modifiers common to all
dumpModifiers(m.modifiers_, indent);
Iterator iter = m.params_.iterator();
while (iter.hasNext()) {
dumpParam((ParamAPI)(iter.next()), indent + indentInc);
}
// Display documentation
if (m.doc_ != null) {
System.out.print("Method doc block:");
System.out.println("\"" + m.doc_ + "\"");
}
}
/**
* Display the contents of a field.
* Does not show inherited fields.
*
* @param f The given field object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpField(FieldAPI f, int indent) {
if (f.inheritedFrom_ != null)
return;
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Field Name: " + f.name_ + ", type: " + f.type_);
if (f.inheritedFrom_ != null)
System.out.println(", inherited from: " + f.inheritedFrom_);
if (f.isTransient_)
System.out.print("transient ");
if (f.isVolatile_)
System.out.print("volatile ");
// Dump modifiers common to all
dumpModifiers(f.modifiers_, indent);
// Display documentation
if (f.doc_ != null)
System.out.print("Field doc block:");
System.out.println("\"" + f.doc_ + "\"");
}
/**
* Display the contents of a parameter.
*
* @param p The given parameter object.
* @param indent The number of spaces to indent the output.
*/
public static void dumpParam(ParamAPI p, int indent) {
for (int i = 0; i < indent; i++) System.out.print(" ");
System.out.println("Param Name: " + p.name_ + ", type: " + p.type_);
}
/**
* Convert all HTML tags to text by placing them inside a CDATA element.
* Characters still have to be valid Unicode characters as defined by the
* parser.
*/
public static String stuffHTMLTags(String htmlText) {
if (htmlText.indexOf("]]>") != -1) {
System.out.println("Warning: illegal string ]]> found in text. Ignoring the comment.");
return "";
}
return "<![CDATA[" + htmlText + "]]>";
}
/**
* Convert all HTML tags to text by stuffing text into the HTML tag
* to stop it being an HTML or XML tag. E.g. &quot;<code>foo</code>&quot;
* becomes &quot;lEsS_tHaNcode>foolEsS_tHaN/code>&quot;. Replace all &lt;
* characters
* with the string "lEsS_tHaN". Also replace &amp; character with the
* string "aNd_cHaR" to avoid text entities. Also replace &quot;
* character with the
* string "qUoTe_cHaR".
*/
public static String hideHTMLTags(String htmlText) {
StringBuffer sb = new StringBuffer(htmlText);
int i = 0;
while (i < sb.length()) {
if (sb.charAt(i) == '<') {
sb.setCharAt(i ,'l');
sb.insert(i+1, "EsS_tHaN");
} else if (sb.charAt(i) == '&') {
sb.setCharAt(i ,'a');
sb.insert(i+1, "Nd_cHaR");
} else if (sb.charAt(i) == '"') {
sb.setCharAt(i ,'q');
sb.insert(i+1, "uote_cHaR");
}
i++;
}
return sb.toString();
}
/**
* Convert text with stuffed HTML tags ("lEsS_tHaN", etc) into HTML text.
*/
public static String showHTMLTags(String text) {
StringBuffer sb = new StringBuffer(text);
StringBuffer res = new StringBuffer();
int len = sb.length();
res.setLength(len);
int i = 0;
int resIdx = 0;
while (i < len) {
char c = sb.charAt(i);
if (len - i > 8 && c == 'l' &&
sb.charAt(i+1) == 'E' &&
sb.charAt(i+2) == 's' &&
sb.charAt(i+3) == 'S' &&
sb.charAt(i+4) == '_' &&
sb.charAt(i+5) == 't' &&
sb.charAt(i+6) == 'H' &&
sb.charAt(i+7) == 'a' &&
sb.charAt(i+8) == 'N') {
res.setCharAt(resIdx ,'<');
i += 8;
} else if (len - i > 9 && c == 'q' &&
sb.charAt(i+1) == 'U' &&
sb.charAt(i+2) == 'o' &&
sb.charAt(i+3) == 'T' &&
sb.charAt(i+4) == 'e' &&
sb.charAt(i+5) == '_' &&
sb.charAt(i+6) == 'c' &&
sb.charAt(i+7) == 'H' &&
sb.charAt(i+8) == 'a' &&
sb.charAt(i+9) == 'R') {
res.setCharAt(resIdx ,'"');
i += 9;
} else if (len - i > 7 && c == 'a' &&
sb.charAt(i+1) == 'N' &&
sb.charAt(i+2) == 'd' &&
sb.charAt(i+3) == '_' &&
sb.charAt(i+4) == 'c' &&
sb.charAt(i+5) == 'H' &&
sb.charAt(i+6) == 'a' &&
sb.charAt(i+7) == 'R') {
res.setCharAt(resIdx ,'&');
i += 7;
} else {
res.setCharAt(resIdx, c);
}
i++;
resIdx++;
}
res.setLength(resIdx);
return res.toString();
}
/**
* <b>NOT USED</b>.
*
* Replace all instances of <p> with <p/>. Just for the small number
* of HMTL tags which don't require a matching end tag.
* Also make HTML conform to the simple HTML requirements such as
* no double hyphens. Double hyphens are replaced by - and the character
* entity for a hyphen.
*
* Cases where this fails and has to be corrected in the XML by hand:
* Attributes' values missing their double quotes , e.g. size=-2
* Mangled HTML tags e.g. &lt;ttt>
*
* <p><b>NOT USED</b>. There is often too much bad HTML in
* doc blocks to try to handle every case correctly. Better just to
* stuff the *lt; and &amp: characters with stuffHTMLTags(). Though
* the resulting XML is not as elegant, it does the job with less
* intervention by the user.
*/
public static String convertHTMLTagsToXHTML(String htmlText) {
StringBuffer sb = new StringBuffer(htmlText);
int i = 0;
boolean inTag = false;
String tag = null;
// Needs to re-evaluate this length at each loop
while (i < sb.length()) {
char c = sb.charAt(i);
if (inTag) {
if (c == '>') {
// OPTION Could fail at or fix some errorneous tags here
// Make the best guess as to whether this tag is terminated
if (Comments.isMinimizedTag(tag) &&
htmlText.indexOf("</" + tag + ">", i) == -1)
sb.insert(i, "/");
inTag = false;
} else {
// OPTION could also make sure that attribute values are
// surrounded by quotes.
tag += c;
}
}
if (c == '<') {
inTag = true;
tag = "";
}
// -- is not allowed in XML, but !-- is part of an comment,
// and --> is also part of a comment
if (c == '-' && i > 0 && sb.charAt(i-1) == '-') {
if (!(i > 1 && sb.charAt(i-2) == '!')) {
sb.setCharAt(i, '&');
sb.insert(i+1, "#045;");
i += 5;
}
}
i++;
}
if (inTag) {
// Oops. Someone forgot to close their HTML tag, e.g. "<code."
// Close it for them.
sb.insert(i, ">");
}
return sb.toString();
}
}
package jdiff;
import java.util.*;
/**
* This class contains method to compare two API objects.
* The differences are stored in an APIDiff object.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class APIComparator {
/**
* Top-level object representing the differences between two APIs.
* It is this object which is used to generate the report later on.
*/
public APIDiff apiDiff;
/**
* Package-level object representing the differences between two packages.
* This object is also used to determine which file to write documentation
* differences into.
*/
public PackageDiff pkgDiff;
/** Default constructor. */
public APIComparator() {
apiDiff = new APIDiff();
}
/** For easy local access to the old API object. */
private static API oldAPI_;
/** For easy local access to the new API object. */
private static API newAPI_;
/**
* Compare two APIs.
*/
public void compareAPIs(API oldAPI, API newAPI) {
System.out.println("JDiff: comparing the old and new APIs ...");
oldAPI_ = oldAPI;
newAPI_ = newAPI;
double differs = 0.0;
apiDiff.oldAPIName_ = oldAPI.name_;
apiDiff.newAPIName_ = newAPI.name_;
Collections.sort(oldAPI.packages_);
Collections.sort(newAPI.packages_);
// Find packages which were removed in the new API
Iterator iter = oldAPI.packages_.iterator();
while (iter.hasNext()) {
PackageAPI oldPkg = (PackageAPI)(iter.next());
// This search is looking for an *exact* match. This is true in
// all the *API classes.
int idx = Collections.binarySearch(newAPI.packages_, oldPkg);
if (idx < 0) {
// If there an instance of a package with the same name
// in both the old and new API, then treat it as changed,
// rather than removed and added. There will never be more than
// one instance of a package with the same name in an API.
int existsNew = newAPI.packages_.indexOf(oldPkg);
if (existsNew != -1) {
// Package by the same name exists in both APIs
// but there has been some or other change.
differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(existsNew)));
} else {
if (trace)
System.out.println("Package " + oldPkg.name_ + " was removed");
apiDiff.packagesRemoved.add(oldPkg);
differs += 1.0;
}
} else {
// The package exists unchanged in name or doc, but may
// differ in classes and their members, so it still needs to
// be compared.
differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(idx)));
}
} // while (iter.hasNext())
// Find packages which were added or changed in the new API
iter = newAPI.packages_.iterator();
while (iter.hasNext()) {
PackageAPI newPkg = (PackageAPI)(iter.next());
int idx = Collections.binarySearch(oldAPI.packages_, newPkg);
if (idx < 0) {
// See comments above
int existsOld = oldAPI.packages_.indexOf(newPkg);
if (existsOld != -1) {
// Don't mark a package as added or compare it
// if it was already marked as changed
} else {
if (trace)
System.out.println("Package " + newPkg.name_ + " was added");
apiDiff.packagesAdded.add(newPkg);
differs += 1.0;
}
} else {
// It will already have been compared above.
}
} // while (iter.hasNext())
// Now that the numbers of members removed and added are known
// we can deduce more information about changes.
MergeChanges.mergeRemoveAdd(apiDiff);
// The percent change statistic reported for all elements in each API is
// defined recursively as follows:
//
// %age change = 100 * (added + removed + 2*changed)
// -----------------------------------
// sum of public elements in BOTH APIs
//
// The definition ensures that if all classes are removed and all new classes
// added, the change will be 100%.
// Evaluation of the visibility of elements has already been done when the
// XML was written out.
// Note that this doesn't count changes in the modifiers of classes and
// packages. Other changes in members are counted.
Long denom = new Long(oldAPI.packages_.size() + newAPI.packages_.size());
// This should never be zero because an API always has packages?
if (denom.intValue() == 0) {
System.out.println("Error: no packages found in the APIs.");
return;
}
if (trace)
System.out.println("Top level changes: " + differs + "/" + denom.intValue());
differs = (100.0 * differs)/denom.doubleValue();
// Some differences such as documentation changes are not tracked in
// the difference statistic, so a value of 0.0 does not mean that there
// were no differences between the APIs.
apiDiff.pdiff = differs;
Double percentage = new Double(differs);
int approxPercentage = percentage.intValue();
if (approxPercentage == 0)
System.out.println(" Approximately " + percentage + "% difference between the APIs");
else
System.out.println(" Approximately " + approxPercentage + "% difference between the APIs");
Diff.closeDiffFile();
}
/**
* Compare two packages.
*/
public double comparePackages(PackageAPI oldPkg, PackageAPI newPkg) {
if (trace)
System.out.println("Comparing old package " + oldPkg.name_ +
" and new package " + newPkg.name_);
pkgDiff = new PackageDiff(oldPkg.name_);
double differs = 0.0;
Collections.sort(oldPkg.classes_);
Collections.sort(newPkg.classes_);
// Find classes which were removed in the new package
Iterator iter = oldPkg.classes_.iterator();
while (iter.hasNext()) {
ClassAPI oldClass = (ClassAPI)(iter.next());
// This search is looking for an *exact* match. This is true in
// all the *API classes.
int idx = Collections.binarySearch(newPkg.classes_, oldClass);
if (idx < 0) {
// If there an instance of a class with the same name
// in both the old and new package, then treat it as changed,
// rather than removed and added. There will never be more than
// one instance of a class with the same name in a package.
int existsNew = newPkg.classes_.indexOf(oldClass);
if (existsNew != -1) {
// Class by the same name exists in both packages
// but there has been some or other change.
differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(existsNew)), pkgDiff);
} else {
if (trace)
System.out.println(" Class " + oldClass.name_ + " was removed");
pkgDiff.classesRemoved.add(oldClass);
differs += 1.0;
}
} else {
// The class exists unchanged in name or modifiers, but may
// differ in members, so it still needs to be compared.
differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(idx)), pkgDiff);
}
} // while (iter.hasNext())
// Find classes which were added or changed in the new package
iter = newPkg.classes_.iterator();
while (iter.hasNext()) {
ClassAPI newClass = (ClassAPI)(iter.next());
int idx = Collections.binarySearch(oldPkg.classes_, newClass);
if (idx < 0) {
// See comments above
int existsOld = oldPkg.classes_.indexOf(newClass);
if (existsOld != -1) {
// Don't mark a class as added or compare it
// if it was already marked as changed
} else {
if (trace)
System.out.println(" Class " + newClass.name_ + " was added");
pkgDiff.classesAdded.add(newClass);
differs += 1.0;
}
} else {
// It will already have been compared above.
}
} // while (iter.hasNext())
// Check if the only change was in documentation. Bug 472521.
boolean differsFlag = false;
if (docChanged(oldPkg.doc_, newPkg.doc_)) {
String link = "<a href=\"pkg_" + oldPkg.name_ + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String id = oldPkg.name_ + "!package";
String title = link + "Package <b>" + oldPkg.name_ + "</b></a>";
pkgDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, null, oldPkg.doc_, newPkg.doc_, id, title);
differsFlag = true;
}
// Only add to the parent Diff object if some difference has been found
if (differs != 0.0 || differsFlag)
apiDiff.packagesChanged.add(pkgDiff);
Long denom = new Long(oldPkg.classes_.size() + newPkg.classes_.size());
// This should never be zero because a package always has classes?
if (denom.intValue() == 0) {
System.out.println("Warning: no classes found in the package " + oldPkg.name_);
return 0.0;
}
if (trace)
System.out.println("Package " + pkgDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
pkgDiff.pdiff = 100.0 * differs/denom.doubleValue();
return differs/denom.doubleValue();
} // comparePackages()
/**
* Compare two classes.
*
* Need to compare constructors, methods and fields.
*/
public double compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff) {
if (trace)
System.out.println(" Comparing old class " + oldClass.name_ +
" and new class " + newClass.name_);
boolean differsFlag = false;
double differs = 0.0;
ClassDiff classDiff = new ClassDiff(oldClass.name_);
classDiff.isInterface_ = newClass.isInterface_; // Used in the report
// Track changes in modifiers - class or interface
if (oldClass.isInterface_ != newClass.isInterface_) {
classDiff.modifiersChange_ = "Changed from ";
if (oldClass.isInterface_)
classDiff.modifiersChange_ += "an interface to a class.";
else
classDiff.modifiersChange_ += "a class to an interface.";
differsFlag = true;
}
// Track changes in inheritance
String inheritanceChange = ClassDiff.diff(oldClass, newClass);
if (inheritanceChange != null) {
classDiff.inheritanceChange_ = inheritanceChange;
differsFlag = true;
}
// Abstract or not
if (oldClass.isAbstract_ != newClass.isAbstract_) {
String changeText = "";
if (oldClass.isAbstract_)
changeText += "Changed from abstract to non-abstract.";
else
changeText += "Changed from non-abstract to abstract.";
classDiff.addModifiersChange(changeText);
differsFlag = true;
}
// Track changes in documentation
if (docChanged(oldClass.doc_, newClass.doc_)) {
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + "!class";
String title = link + "Class <b>" + classDiff.name_ + "</b></a>";
classDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_,
classDiff.name_, oldClass.doc_, newClass.doc_, id, title);
differsFlag = true;
}
// All other modifiers
String modifiersChange = oldClass.modifiers_.diff(newClass.modifiers_);
if (modifiersChange != null) {
differsFlag = true;
if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + pkgDiff.name_ + "." + newClass.name_);
}
}
classDiff.addModifiersChange(modifiersChange);
// Track changes in members
boolean differsCtors =
compareAllCtors(oldClass, newClass, classDiff);
boolean differsMethods =
compareAllMethods(oldClass, newClass, classDiff);
boolean differsFields =
compareAllFields(oldClass, newClass, classDiff);
if (differsCtors || differsMethods || differsFields)
differsFlag = true;
if (trace) {
System.out.println(" Ctors differ? " + differsCtors +
", Methods differ? " + differsMethods +
", Fields differ? " + differsFields);
}
// Only add to the parent if some difference has been found
if (differsFlag)
pkgDiff.classesChanged.add(classDiff);
// Get the numbers of affected elements from the classDiff object
differs =
classDiff.ctorsRemoved.size() + classDiff.ctorsAdded.size() +
classDiff.ctorsChanged.size() +
classDiff.methodsRemoved.size() + classDiff.methodsAdded.size() +
classDiff.methodsChanged.size() +
classDiff.fieldsRemoved.size() + classDiff.fieldsAdded.size() +
classDiff.fieldsChanged.size();
Long denom = new Long(
oldClass.ctors_.size() +
numLocalMethods(oldClass.methods_) +
numLocalFields(oldClass.fields_) +
newClass.ctors_.size() +
numLocalMethods(newClass.methods_) +
numLocalFields(newClass.fields_));
if (denom.intValue() == 0) {
// This is probably a placeholder interface, but documentation
// or modifiers etc may have changed
if (differsFlag) {
classDiff.pdiff = 0.0; // 100.0 is too much
return 1.0;
} else {
return 0.0;
}
}
// Handle the case where the only change is in documentation or
// the modifiers
if (differsFlag && differs == 0.0) {
differs = 1.0;
}
if (trace)
System.out.println(" Class " + classDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
classDiff.pdiff = 100.0 * differs/denom.doubleValue();
return differs/denom.doubleValue();
} // compareClasses()
/**
* Compare all the constructors in two classes.
*
* The compareTo method in the ConstructorAPI class acts only upon the type.
*/
public boolean compareAllCtors(ClassAPI oldClass, ClassAPI newClass,
ClassDiff classDiff) {
if (trace)
System.out.println(" Comparing constructors: #old " +
oldClass.ctors_.size() + ", #new " + newClass.ctors_.size());
boolean differs = false;
boolean singleCtor = false; // Set if there is only one ctor
Collections.sort(oldClass.ctors_);
Collections.sort(newClass.ctors_);
// Find ctors which were removed in the new class
Iterator iter = oldClass.ctors_.iterator();
while (iter.hasNext()) {
ConstructorAPI oldCtor = (ConstructorAPI)(iter.next());
int idx = Collections.binarySearch(newClass.ctors_, oldCtor);
if (idx < 0) {
int oldSize = oldClass.ctors_.size();
int newSize = newClass.ctors_.size();
if (oldSize == 1 && oldSize == newSize) {
// If there is one constructor in the oldClass and one
// constructor in the new class, then mark it as changed
MemberDiff memberDiff = new MemberDiff(oldClass.name_);
memberDiff.oldType_ = oldCtor.type_;
memberDiff.oldExceptions_ = oldCtor.exceptions_;
ConstructorAPI newCtor = (ConstructorAPI)(newClass.ctors_.get(0));
memberDiff.newType_ = newCtor.type_;
memberDiff.newExceptions_ = newCtor.exceptions_;
// Track changes in documentation
if (docChanged(oldCtor.doc_, newCtor.doc_)) {
String type = memberDiff.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>";
memberDiff.documentationChange_ = Diff.saveDocDiffs(
pkgDiff.name_, classDiff.name_, oldCtor.doc_, newCtor.doc_, id, title);
}
String modifiersChange = oldCtor.modifiers_.diff(newCtor.modifiers_);
if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
System.out.println("JDiff: warning: change from deprecated to undeprecated for a constructor in class" + newClass.name_);
}
memberDiff.addModifiersChange(modifiersChange);
if (trace)
System.out.println(" The single constructor was changed");
classDiff.ctorsChanged.add(memberDiff);
singleCtor = true;
} else {
if (trace)
System.out.println(" Constructor " + oldClass.name_ + " was removed");
classDiff.ctorsRemoved.add(oldCtor);
}
differs = true;
}
} // while (iter.hasNext())
// Find ctors which were added in the new class
iter = newClass.ctors_.iterator();
while (iter.hasNext()) {
ConstructorAPI newCtor = (ConstructorAPI)(iter.next());
int idx = Collections.binarySearch(oldClass.ctors_, newCtor);
if (idx < 0) {
if (!singleCtor) {
if (trace)
System.out.println(" Constructor " + oldClass.name_ + " was added");
classDiff.ctorsAdded.add(newCtor);
differs = true;
}
}
} // while (iter.hasNext())
return differs;
} // compareAllCtors()
/**
* Compare all the methods in two classes.
*
* We have to deal with the cases where:
* - there is only one method with a given name, but its signature changes
* - there is more than one method with the same name, and some of them
* may have signature changes
* The simplest way to deal with this is to make the MethodAPI comparator
* check the params and return type, as well as the name. This means that
* changing a parameter's type would cause the method to be seen as
* removed and added. To avoid this for the simple case, check for before
* recording a method as removed or added.
*/
public boolean compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff) {
if (trace)
System.out.println(" Comparing methods: #old " +
oldClass.methods_.size() + ", #new " +
newClass.methods_.size());
boolean differs = false;
Collections.sort(oldClass.methods_);
Collections.sort(newClass.methods_);
// Find methods which were removed in the new class
Iterator iter = oldClass.methods_.iterator();
while (iter.hasNext()) {
MethodAPI oldMethod = (MethodAPI)(iter.next());
int idx = -1;
MethodAPI[] methodArr = new MethodAPI[newClass.methods_.size()];
methodArr = (MethodAPI[])newClass.methods_.toArray(methodArr);
for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
MethodAPI newMethod = methodArr[methodIdx];
if (oldMethod.compareTo(newMethod) == 0) {
idx = methodIdx;
break;
}
}
// NOTE: there was a problem with the binarySearch for
// java.lang.Byte.toString(byte b) returning -16 when the compareTo method
// returned 0 on entry 13. Changed to use arrays instead, so maybe it was
// an issue with methods having another List of params used indirectly by
// compareTo(), unlike constructors and fields?
// int idx = Collections.binarySearch(newClass.methods_, oldMethod);
if (idx < 0) {
// If there is only one instance of a method with this name
// in both the old and new class, then treat it as changed,
// rather than removed and added.
// Find how many instances of this method name there are in
// the old and new class. The equals comparator is just on
// the method name.
int startOld = oldClass.methods_.indexOf(oldMethod);
int endOld = oldClass.methods_.lastIndexOf(oldMethod);
int startNew = newClass.methods_.indexOf(oldMethod);
int endNew = newClass.methods_.lastIndexOf(oldMethod);
if (startOld != -1 && startOld == endOld &&
startNew != -1 && startNew == endNew) {
MethodAPI newMethod = (MethodAPI)(newClass.methods_.get(startNew));
// Only one method with that name exists in both packages,
// so it is valid to compare the two methods. We know it
// has changed, because the binarySearch did not find it.
if (oldMethod.inheritedFrom_ == null ||
newMethod.inheritedFrom_ == null) {
// We also know that at least one of the methods is
// locally defined.
compareMethods(oldMethod, newMethod, classDiff);
differs = true;
}
} else if (oldMethod.inheritedFrom_ == null) {
// Only concerned with locally defined methods
if (trace)
System.out.println(" Method " + oldMethod.name_ +
"(" + oldMethod.getSignature() +
") was removed");
classDiff.methodsRemoved.add(oldMethod);
differs = true;
}
}
} // while (iter.hasNext())
// Find methods which were added in the new class
iter = newClass.methods_.iterator();
while (iter.hasNext()) {
MethodAPI newMethod = (MethodAPI)(iter.next());
// Only concerned with locally defined methods
if (newMethod.inheritedFrom_ != null)
continue;
int idx = -1;
MethodAPI[] methodArr = new MethodAPI[oldClass.methods_.size()];
methodArr = (MethodAPI[])oldClass.methods_.toArray(methodArr);
for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
MethodAPI oldMethod = methodArr[methodIdx];
if (newMethod.compareTo(oldMethod) == 0) {
idx = methodIdx;
break;
}
}
// See note above about searching an array instead of binarySearch
// int idx = Collections.binarySearch(oldClass.methods_, newMethod);
if (idx < 0) {
// See comments above
int startOld = oldClass.methods_.indexOf(newMethod);
int endOld = oldClass.methods_.lastIndexOf(newMethod);
int startNew = newClass.methods_.indexOf(newMethod);
int endNew = newClass.methods_.lastIndexOf(newMethod);
if (startOld != -1 && startOld == endOld &&
startNew != -1 && startNew == endNew) {
// Don't mark a method as added if it was marked as changed
// The comparison will have been done just above here.
} else {
if (trace)
System.out.println(" Method " + newMethod.name_ +
"(" + newMethod.getSignature() + ") was added");
classDiff.methodsAdded.add(newMethod);
differs = true;
}
}
} // while (iter.hasNext())
return differs;
} // compareAllMethods()
/**
* Compare two methods which have the same name.
*/
public boolean compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff) {
MemberDiff methodDiff = new MemberDiff(oldMethod.name_);
boolean differs = false;
// Check changes in return type
methodDiff.oldType_ = oldMethod.returnType_;
methodDiff.newType_ = newMethod.returnType_;
if (oldMethod.returnType_.compareTo(newMethod.returnType_) != 0) {
differs = true;
}
// Check changes in signature
String oldSig = oldMethod.getSignature();
String newSig = newMethod.getSignature();
methodDiff.oldSignature_ = oldSig;
methodDiff.newSignature_ = newSig;
if (oldSig.compareTo(newSig) != 0) {
differs = true;
}
// Changes in inheritance
int inh = changedInheritance(oldMethod.inheritedFrom_, newMethod.inheritedFrom_);
if (inh != 0)
differs = true;
if (inh == 1) {
methodDiff.addModifiersChange("Method was locally defined, but is now inherited from " + linkToClass(newMethod, true) + ".");
methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
} else if (inh == 2) {
methodDiff.addModifiersChange("Method was inherited from " + linkToClass(oldMethod, false) + ", but is now defined locally.");
} else if (inh == 3) {
methodDiff.addModifiersChange("Method was inherited from " +
linkToClass(oldMethod, false) + ", and is now inherited from " + linkToClass(newMethod, true) + ".");
methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
}
// 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);
differs = true;
}
// 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);
differs = true;
}
// 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);
differs = true;
}
// Check changes in exceptions thrown
methodDiff.oldExceptions_ = oldMethod.exceptions_;
methodDiff.newExceptions_ = newMethod.exceptions_;
if (oldMethod.exceptions_.compareTo(newMethod.exceptions_) != 0) {
differs = true;
}
// Track changes in documentation
if (docChanged(oldMethod.doc_, newMethod.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 + "." + newMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldMethod.doc_, newMethod.doc_, id, title);
differs = true;
}
// All other modifiers
String modifiersChange = oldMethod.modifiers_.diff(newMethod.modifiers_);
if (modifiersChange != null) {
differs = true;
if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
System.out.println("JDiff: warning: change from deprecated to undeprecated for method " + classDiff.name_ + "." + newMethod.name_);
}
}
methodDiff.addModifiersChange(modifiersChange);
// Only add to the parent if some difference has been found
if (differs) {
if (trace) {
System.out.println(" Method " + newMethod.name_ +
" was changed: old: " +
oldMethod.returnType_ + "(" + oldSig + "), new: " +
newMethod.returnType_ + "(" + newSig + ")");
if (methodDiff.modifiersChange_ != null)
System.out.println(" Modifier change: " + methodDiff.modifiersChange_);
}
classDiff.methodsChanged.add(methodDiff);
}
return differs;
} // compareMethods()
/**
* Compare all the fields in two classes.
*/
public boolean compareAllFields(ClassAPI oldClass, ClassAPI newClass,
ClassDiff classDiff) {
if (trace)
System.out.println(" Comparing fields: #old " +
oldClass.fields_.size() + ", #new "
+ newClass.fields_.size());
boolean differs = false;
Collections.sort(oldClass.fields_);
Collections.sort(newClass.fields_);
// Find fields which were removed in the new class
Iterator iter = oldClass.fields_.iterator();
while (iter.hasNext()) {
FieldAPI oldField = (FieldAPI)(iter.next());
int idx = Collections.binarySearch(newClass.fields_, oldField);
if (idx < 0) {
// If there an instance of a field with the same name
// in both the old and new class, then treat it as changed,
// rather than removed and added. There will never be more than
// one instance of a field with the same name in a class.
int existsNew = newClass.fields_.indexOf(oldField);
if (existsNew != -1) {
FieldAPI newField = (FieldAPI)(newClass.fields_.get(existsNew));
if (oldField.inheritedFrom_ == null ||
newField.inheritedFrom_ == null) {
// We also know that one of the fields is locally defined.
MemberDiff memberDiff = new MemberDiff(oldField.name_);
memberDiff.oldType_ = oldField.type_;
memberDiff.newType_ = newField.type_;
// Changes in inheritance
int inh = changedInheritance(oldField.inheritedFrom_, newField.inheritedFrom_);
if (inh != 0)
differs = true;
if (inh == 1) {
memberDiff.addModifiersChange("Field was locally defined, but is now inherited from " + linkToClass(newField, true) + ".");
memberDiff.inheritedFrom_ = newField.inheritedFrom_;
} else if (inh == 2) {
memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", but is now defined locally.");
} else if (inh == 3) {
memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", and is now inherited from " + linkToClass(newField, true) + ".");
memberDiff.inheritedFrom_ = newField.inheritedFrom_;
}
// Transient or not
if (oldField.isTransient_ != newField.isTransient_) {
String changeText = "";
if (oldField.isTransient_)
changeText += "Changed from transient to non-transient.";
else
changeText += "Changed from non-transient to transient.";
memberDiff.addModifiersChange(changeText);
differs = true;
}
// Volatile or not
if (oldField.isVolatile_ != newField.isVolatile_) {
String changeText = "";
if (oldField.isVolatile_)
changeText += "Changed from volatile to non-volatile.";
else
changeText += "Changed from non-volatile to volatile.";
memberDiff.addModifiersChange(changeText);
differs = true;
}
// Change in value of the field
if (oldField.value_ != null &&
newField.value_ != null &&
oldField.value_.compareTo(newField.value_) != 0) {
String changeText = "Changed in value from " + oldField.value_
+ " to " + newField.value_ +".";
memberDiff.addModifiersChange(changeText);
differs = true;
}
// Track changes in documentation
if (docChanged(oldField.doc_, newField.doc_)) {
String fqName = pkgDiff.name_ + "." + classDiff.name_;
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newField.name_ + "\" class=\"hiddenlink\">";
String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + newField.name_;
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
link2 + HTMLReportGenerator.simpleName(memberDiff.newType_) + " <b>" + newField.name_ + "</b></a>";
memberDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldField.doc_, newField.doc_, id, title);
differs = true;
}
// Other differences
String modifiersChange = oldField.modifiers_.diff(newField.modifiers_);
memberDiff.addModifiersChange(modifiersChange);
if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + newClass.name_ + ", field " + newField.name_);
}
if (trace)
System.out.println(" Field " + newField.name_ + " was changed");
classDiff.fieldsChanged.add(memberDiff);
differs = true;
}
} else if (oldField.inheritedFrom_ == null) {
if (trace)
System.out.println(" Field " + oldField.name_ + " was removed");
classDiff.fieldsRemoved.add(oldField);
differs = true;
}
}
} // while (iter.hasNext())
// Find fields which were added in the new class
iter = newClass.fields_.iterator();
while (iter.hasNext()) {
FieldAPI newField = (FieldAPI)(iter.next());
// Only concerned with locally defined fields
if (newField.inheritedFrom_ != null)
continue;
int idx = Collections.binarySearch(oldClass.fields_, newField);
if (idx < 0) {
// See comments above
int existsOld = oldClass.fields_.indexOf(newField);
if (existsOld != -1) {
// Don't mark a field as added if it was marked as changed
} else {
if (trace)
System.out.println(" Field " + newField.name_ + " was added");
classDiff.fieldsAdded.add(newField);
differs = true;
}
}
} // while (iter.hasNext())
return differs;
} // compareFields()
/**
* Decide if two blocks of documentation changed.
*
* @return true if both are non-null and differ,
* or if one is null and the other is not.
*/
public static boolean docChanged(String oldDoc, String newDoc) {
if (!HTMLReportGenerator.reportDocChanges)
return false; // Don't even count doc changes as changes
if (oldDoc == null && newDoc != null)
return true;
if (oldDoc != null && newDoc == null)
return true;
if (oldDoc != null && newDoc != null && oldDoc.compareTo(newDoc) != 0)
return true;
return false;
}
/**
* Decide if two elements changed where they were defined.
*
* @return 0 if both are null, or both are non-null and are the same.
* 1 if the oldInherit was null and newInherit is non-null.
* 2 if the oldInherit was non-null and newInherit is null.
* 3 if the oldInherit was non-null and newInherit is non-null
* and they differ.
*/
public static int changedInheritance(String oldInherit, String newInherit) {
if (oldInherit == null && newInherit == null)
return 0;
if (oldInherit == null && newInherit != null)
return 1;
if (oldInherit != null && newInherit == null)
return 2;
if (oldInherit.compareTo(newInherit) == 0)
return 0;
else
return 3;
}
/**
* Generate a link to the Javadoc page for the given method.
*/
public static String linkToClass(MethodAPI m, boolean useNew) {
String sig = m.getSignature();
if (sig.compareTo("void") == 0)
sig = "";
return linkToClass(m.inheritedFrom_, m.name_, sig, useNew);
}
/**
* Generate a link to the Javadoc page for the given field.
*/
public static String linkToClass(FieldAPI m, boolean useNew) {
return linkToClass(m.inheritedFrom_, m.name_, null, useNew);
}
/**
* Given the name of the class, generate a link to a relevant page.
* This was originally for inheritance changes, so the JDiff page could
* be a class changes page, or a section in a removed or added classes
* table. Since there was no easy way to tell which type the link
* should be, it is now just a link to the relevant Javadoc page.
*/
public static String linkToClass(String className, String memberName,
String memberType, boolean useNew) {
if (!useNew && HTMLReportGenerator.oldDocPrefix == null) {
return "<tt>" + className + "</tt>"; // No link possible
}
API api = oldAPI_;
String prefix = HTMLReportGenerator.oldDocPrefix;
if (useNew) {
api = newAPI_;
prefix = HTMLReportGenerator.newDocPrefix;
}
ClassAPI cls = (ClassAPI)api.classes_.get(className);
if (cls == null) {
if (useNew)
System.out.println("Warning: class " + className + " not found in the new API when creating Javadoc link");
else
System.out.println("Warning: class " + className + " not found in the old API when creating Javadoc link");
return "<tt>" + className + "</tt>";
}
int clsIdx = className.indexOf(cls.name_);
if (clsIdx != -1) {
String pkgRef = className.substring(0, clsIdx);
pkgRef = pkgRef.replace('.', '/');
String res = "<a href=\"" + prefix + pkgRef + cls.name_ + ".html#" + memberName;
if (memberType != null)
res += "(" + memberType + ")";
res += "\" target=\"_top\">" + "<tt>" + cls.name_ + "</tt></a>";
return res;
}
return "<tt>" + className + "</tt>";
}
/**
* Return the number of methods which are locally defined.
*/
public int numLocalMethods(List methods) {
int res = 0;
Iterator iter = methods.iterator();
while (iter.hasNext()) {
MethodAPI m = (MethodAPI)(iter.next());
if (m.inheritedFrom_ == null)
res++;
}
return res;
}
/**
* Return the number of fields which are locally defined.
*/
public int numLocalFields(List fields) {
int res = 0;
Iterator iter = fields.iterator();
while (iter.hasNext()) {
FieldAPI f = (FieldAPI)(iter.next());
if (f.inheritedFrom_ == null)
res++;
}
return res;
}
/** Set to enable increased logging verbosity for debugging. */
private boolean trace = false;
}
package jdiff;
import java.util.*;
import com.sun.javadoc.*;
/**
* The class contains the changes between two API objects; packages added,
* removed and changed. The packages are represented by PackageDiff objects,
* which contain the changes in each package, and so on.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class APIDiff {
/** Packages added in the new API. */
public List packagesAdded = null; // PackageAPI[]
/** Packages removed in the new API. */
public List packagesRemoved = null; // PackageAPI[]
/** Packages changed in the new API. */
public List packagesChanged = null; // PackageDiff[]
/** Name of the old API. */
public static String oldAPIName_;
/** Name of the old API. */
public static String newAPIName_;
/* The overall percentage difference between the two APIs. */
public double pdiff = 0.0;
/** Default constructor. */
public APIDiff() {
oldAPIName_ = null;
newAPIName_ = null;
packagesAdded = new ArrayList(); // PackageAPI[]
packagesRemoved = new ArrayList(); // PackageAPI[]
packagesChanged = new ArrayList(); // PackageDiff[]
}
}
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.helpers.DefaultHandler;
/**
* Handle the parsing of an XML file and the generation of an API object.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class APIHandler extends DefaultHandler {
/** The API object which is populated from the XML file. */
public API api_;
/** Default constructor. */
public APIHandler(API api, boolean createGlobalComments) {
api_ = api;
createGlobalComments_ = createGlobalComments;
tagStack = new LinkedList();
}
/** If set, then check that each comment is a sentence. */
public static boolean checkIsSentence = false;
/**
* Contains the name of the current package element type
* where documentation is being added. Also used as the level
* at which to add documentation into an element, i.e. class-level
* or package-level.
*/
private String currentElement = null;
/** If set, then create the global list of comments. */
private boolean createGlobalComments_ = false;
/** Set if inside a doc element. */
private boolean inDoc = false;
/** The current comment text being assembled. */
private String currentText = null;
/** The current text from deprecation, null if empty. */
private String currentDepText = null;
/**
* The stack of SingleComment objects awaiting the comment text
* currently being assembled.
*/
private LinkedList tagStack = null;
/** Called at the start of the document. */
public void startDocument() {
}
/** Called when the end of the document is reached. */
public void endDocument() {
if (trace)
api_.dump();
System.out.println(" finished");
}
/** Called when a new element is started. */
public void startElement(java.lang.String uri, java.lang.String localName,
java.lang.String qName, Attributes attributes) {
// The change to JAXP compliance produced this change.
if (localName.equals(""))
localName = qName;
if (localName.compareTo("api") == 0) {
String apiName = attributes.getValue("name");
String version = attributes.getValue("jdversion"); // Not used yet
XMLToAPI.nameAPI(apiName);
} else if (localName.compareTo("package") == 0) {
currentElement = localName;
String pkgName = attributes.getValue("name");
XMLToAPI.addPackage(pkgName);
} else if (localName.compareTo("class") == 0) {
currentElement = localName;
String className = attributes.getValue("name");
String parentName = attributes.getValue("extends");
boolean isAbstract = false;
if (attributes.getValue("abstract").compareTo("true") == 0)
isAbstract = true;
XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes));
} else if (localName.compareTo("interface") == 0) {
currentElement = localName;
String className = attributes.getValue("name");
String parentName = attributes.getValue("extends");
boolean isAbstract = false;
if (attributes.getValue("abstract").compareTo("true") == 0)
isAbstract = true;
XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes));
} else if (localName.compareTo("implements") == 0) {
String interfaceName = attributes.getValue("name");
XMLToAPI.addImplements(interfaceName);
} else if (localName.compareTo("constructor") == 0) {
currentElement = localName;
String ctorType = attributes.getValue("type");
XMLToAPI.addCtor(ctorType, getModifiers(attributes));
} else if (localName.compareTo("method") == 0) {
currentElement = localName;
String methodName = attributes.getValue("name");
String returnType = attributes.getValue("return");
boolean isAbstract = false;
if (attributes.getValue("abstract").compareTo("true") == 0)
isAbstract = true;
boolean isNative = false;
if (attributes.getValue("native").compareTo("true") == 0)
isNative = true;
boolean isSynchronized = false;
if (attributes.getValue("synchronized").compareTo("true") == 0)
isSynchronized = true;
XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative,
isSynchronized, getModifiers(attributes));
} else if (localName.compareTo("field") == 0) {
currentElement = localName;
String fieldName = attributes.getValue("name");
String fieldType = attributes.getValue("type");
boolean isTransient = false;
if (attributes.getValue("transient").compareTo("true") == 0)
isTransient = true;
boolean isVolatile = false;
if (attributes.getValue("volatile").compareTo("true") == 0)
isVolatile = true;
String value = attributes.getValue("value");
XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile,
value, getModifiers(attributes));
} else if (localName.compareTo("param") == 0) {
String paramName = attributes.getValue("name");
String paramType = attributes.getValue("type");
XMLToAPI.addParam(paramName, paramType);
} else if (localName.compareTo("exception") == 0) {
String paramName = attributes.getValue("name");
String paramType = attributes.getValue("type");
XMLToAPI.addException(paramName, paramType, currentElement);
} else if (localName.compareTo("doc") == 0) {
inDoc = true;
currentText = null;
} else {
if (inDoc) {
// Start of an element, probably an HTML element
addStartTagToText(localName, attributes);
} else {
System.out.println("Error: unknown element type: " + localName);
System.exit(-1);
}
}
}
/** Called when the end of an element is reached. */
public void endElement(java.lang.String uri, java.lang.String localName,
java.lang.String qName) {
if (localName.equals(""))
localName = qName;
// Deal with the end of doc blocks
if (localName.compareTo("doc") == 0) {
inDoc = false;
// Add the assembled comment text to the appropriate current
// program element, as determined by currentElement.
addTextToComments();
} else if (inDoc) {
// An element was found inside the HTML text
addEndTagToText(localName);
} else if (currentElement.compareTo("constructor") == 0 &&
localName.compareTo("constructor") == 0) {
currentElement = "class";
} else if (currentElement.compareTo("method") == 0 &&
localName.compareTo("method") == 0) {
currentElement = "class";
} else if (currentElement.compareTo("field") == 0 &&
localName.compareTo("field") == 0) {
currentElement = "class";
} else if (currentElement.compareTo("class") == 0 ||
currentElement.compareTo("interface") == 0) {
// Feature request 510307 and bug 517383: duplicate comment ids.
// The end of a member element leaves the currentElement at the
// "class" level, but the next class may in fact be an interface
// and so the currentElement here will be "interface".
if (localName.compareTo("class") == 0 ||
localName.compareTo("interface") == 0) {
currentElement = "package";
}
}
}
/** Called to process text. */
public void characters(char[] ch, int start, int length) {
if (inDoc) {
String chunk = new String(ch, start, length);
if (currentText == null)
currentText = chunk;
else
currentText += chunk;
}
}
/**
* Trim the current text, check it is a sentence and add it to the
* current program element.
*/
public void addTextToComments() {
// Eliminate any whitespace at each end of the text.
currentText = currentText.trim();
// Convert any @link tags to HTML links.
if (convertAtLinks) {
currentText = Comments.convertAtLinks(currentText, currentElement,
api_.currPkg_, api_.currClass_);
}
// Check that it is a sentence
if (checkIsSentence && !currentText.endsWith(".") &&
currentText.compareTo(Comments.placeHolderText) != 0) {
System.out.println("Warning: text of comment does not end in a period: " + currentText);
}
// The construction of the commentID assumes that the
// documentation is the final element to be parsed. The format matches
// the format used in the report generator to look up comments in the
// the existingComments object.
String commentID = null;
// Add this comment to the current API element.
if (currentElement.compareTo("package") == 0) {
api_.currPkg_.doc_ = currentText;
commentID = api_.currPkg_.name_;
} else if (currentElement.compareTo("class") == 0 ||
currentElement.compareTo("interface") == 0) {
api_.currClass_.doc_ = currentText;
commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_;
} else if (currentElement.compareTo("constructor") == 0) {
api_.currCtor_.doc_ = currentText;
commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
".ctor_changed(";
if (api_.currCtor_.type_.compareTo("void") == 0)
commentID = commentID + ")";
else
commentID = commentID + api_.currCtor_.type_ + ")";
} else if (currentElement.compareTo("method") == 0) {
api_.currMethod_.doc_ = currentText;
commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
"." + api_.currMethod_.name_ + "_changed(" +
api_.currMethod_.getSignature() + ")";
} else if (currentElement.compareTo("field") == 0) {
api_.currField_.doc_ = currentText;
commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
"." + api_.currField_.name_;
}
// Add to the list of possible comments for use when an
// element has changed (not removed or added).
if (createGlobalComments_ && commentID != null) {
String ct = currentText;
// Use any deprecation text as the possible comment, ignoring
// any other comment text.
if (currentDepText != null) {
ct = currentDepText;
currentDepText = null; // Never reuse it. Bug 469794
}
String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct));
if (ctOld != null) {
System.out.println("Error: duplicate comment id: " + commentID);
System.exit(5);
}
}
}
/**
* Add the start tag to the current comment text.
*/
public void addStartTagToText(String localName, Attributes attributes) {
// Need to insert the HTML tag into the current text
String currentHTMLTag = localName;
// Save the tag in a stack
tagStack.add(currentHTMLTag);
String tag = "<" + currentHTMLTag;
// Now add all the attributes into the current text
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
tag += " " + name + "=\"" + value+ "\"";
}
// End the tag
if (Comments.isMinimizedTag(currentHTMLTag)) {
tag += "/>";
} else {
tag += ">";
}
// Now insert the HTML tag into the current text
if (currentText == null)
currentText = tag;
else
currentText += tag;
}
/**
* Add the end tag to the current comment text.
*/
public void addEndTagToText(String localName) {
// Close the current HTML tag
String currentHTMLTag = (String)(tagStack.removeLast());
if (!Comments.isMinimizedTag(currentHTMLTag))
currentText += "</" + currentHTMLTag + ">";
}
/** Extra modifiers which are common to all program elements. */
public Modifiers getModifiers(Attributes attributes) {
Modifiers modifiers = new Modifiers();
modifiers.isStatic = false;
if (attributes.getValue("static").compareTo("true") == 0)
modifiers.isStatic = true;
modifiers.isFinal = false;
if (attributes.getValue("final").compareTo("true") == 0)
modifiers.isFinal = true;
modifiers.isDeprecated = false;
String cdt = attributes.getValue("deprecated");
if (cdt.compareTo("not deprecated") == 0) {
modifiers.isDeprecated = false;
currentDepText = null;
} else if (cdt.compareTo("deprecated, no comment") == 0) {
modifiers.isDeprecated = true;
currentDepText = null;
} else {
modifiers.isDeprecated = true;
currentDepText = API.showHTMLTags(cdt);
}
modifiers.visibility = attributes.getValue("visibility");
return modifiers;
}
public void warning(SAXParseException e) {
System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e);
e.printStackTrace();
}
public void error(SAXParseException e) {
System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
e.printStackTrace();
System.exit(1);
}
public void fatalError(SAXParseException e) {
System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
e.printStackTrace();
System.exit(1);
}
/**
* If set, then attempt to convert @link tags to HTML links.
* A few of the HTML links may be broken links.
*/
private static boolean convertAtLinks = true;
/** 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 class, analogous to ClassDoc in the
* Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this class.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class ClassAPI implements Comparable {
/** Name of the class, not fully qualified. */
public String name_;
/** Set if this class is an interface. */
public boolean isInterface_;
/** Set if this class is abstract. */
boolean isAbstract_ = false;
/** Modifiers for this class. */
public Modifiers modifiers_;
/** Name of the parent class, or null if there is no parent. */
public String extends_; // Can only extend zero or one class or interface
/** Interfaces implemented by this class. */
public List implements_; // String[]
/** Constructors in this class. */
public List ctors_; // ConstructorAPI[]
/** Methods in this class. */
public List methods_; // MethodAPI[]
/** Fields in this class. */
public List fields_; //FieldAPI[]
/** The doc block, default is null. */
public String doc_ = null;
/** Constructor. */
public ClassAPI(String name, String parent, boolean isInterface,
boolean isAbstract, Modifiers modifiers) {
name_ = name;
extends_ = parent;
isInterface_ = isInterface;
isAbstract_ = isAbstract;
modifiers_ = modifiers;
implements_ = new ArrayList(); // String[]
ctors_ = new ArrayList(); // ConstructorAPI[]
methods_ = new ArrayList(); // MethodAPI[]
fields_ = new ArrayList(); // FieldAPI[]
}
/** Compare two ClassAPI objects by all the known information. */
public int compareTo(Object o) {
ClassAPI oClassAPI = (ClassAPI)o;
int comp = name_.compareTo(oClassAPI.name_);
if (comp != 0)
return comp;
if (isInterface_ != oClassAPI.isInterface_)
return -1;
if (isAbstract_ != oClassAPI.isAbstract_)
return -1;
comp = modifiers_.compareTo(oClassAPI.modifiers_);
if (comp != 0)
return comp;
if (APIComparator.docChanged(doc_, oClassAPI.doc_))
return -1;
return 0;
}
/**
* Tests two methods for equality using just the class name,
* used by indexOf().
*/
public boolean equals(Object o) {
if (name_.compareTo(((ClassAPI)o).name_) == 0)
return true;
return false;
}
}
package jdiff;
import java.util.*;
import com.sun.javadoc.*;
/**
* The changes between two classes.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class ClassDiff {
/** Name of the class. */
public String name_;
/** Set if this class is an interface in the new API. */
public boolean isInterface_;
/**
* A string describing the changes in inheritance.
*/
public String inheritanceChange_ = 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 a class or interface, whether it is
* abstract, static, final, and in its visibility.
*/
public String modifiersChange_ = null;
/** Constructors added in the new API. */
public List ctorsAdded = null;
/** Constructors removed in the new API. */
public List ctorsRemoved = null;
/** Constructors changed in the new API. */
public List ctorsChanged = null;
/** Methods added in the new API. */
public List methodsAdded = null;
/** Methods removed in the new API. */
public List methodsRemoved = null;
/** Methods changed in the new API. */
public List methodsChanged = null;
/** Fields added in the new API. */
public List fieldsAdded = null;
/** Fields removed in the new API. */
public List fieldsRemoved = null;
/** Fields changed in the new API. */
public List fieldsChanged = null;
/* The percentage difference for this class. */
public double pdiff = 0.0;
/** Default constructor. */
public ClassDiff(String name) {
name_ = name;
isInterface_ = false;
ctorsAdded = new ArrayList(); // ConstructorAPI[]
ctorsRemoved = new ArrayList(); // ConstructorAPI[]
ctorsChanged = new ArrayList(); // MemberDiff[]
methodsAdded = new ArrayList(); // MethodAPI[]
methodsRemoved = new ArrayList(); // MethodAPI[]
methodsChanged = new ArrayList(); // MemberDiff[]
fieldsAdded = new ArrayList(); // FieldAPI[]
fieldsRemoved = new ArrayList(); // FieldAPI[]
fieldsChanged = new ArrayList(); // MemberDiff[]
}
/**
* Compare the inheritance details of two classes and produce
* a String for the inheritanceChanges_ field in this class.
* If there is no difference, null is returned.
*/
public static String diff(ClassAPI oldClass, ClassAPI newClass) {
Collections.sort(oldClass.implements_);
Collections.sort(newClass.implements_);
String res = "";
boolean hasContent = false;
if (oldClass.extends_ != null && newClass.extends_ != null &&
oldClass.extends_.compareTo(newClass.extends_) != 0) {
res += "The superclass changed from <code>" + oldClass.extends_ + "</code> to <code>" + newClass.extends_ + "</code>.<br>";
hasContent = true;
}
// Check for implemented interfaces which were removed
String removedInterfaces = "";
int numRemoved = 0;
Iterator iter = oldClass.implements_.iterator();
while (iter.hasNext()) {
String oldInterface = (String)(iter.next());
int idx = Collections.binarySearch(newClass.implements_, oldInterface);
if (idx < 0) {
if (numRemoved != 0)
removedInterfaces += ", ";
removedInterfaces += oldInterface;
numRemoved++;
}
}
String addedInterfaces = "";
int numAdded = 0;
iter = newClass.implements_.iterator();
while (iter.hasNext()) {
String newInterface = (String)(iter.next());
int idx = Collections.binarySearch(oldClass.implements_, newInterface);
if (idx < 0) {
if (numAdded != 0)
addedInterfaces += ", ";
addedInterfaces += newInterface;
numAdded++;
}
}
if (numRemoved != 0) {
if (hasContent)
res += " ";
if (numRemoved == 1)
res += "Removed interface <code>" + removedInterfaces + "</code>.<br>";
else
res += "Removed interfaces <code>" + removedInterfaces + "</code>.<br>";
hasContent = true;
}
if (numAdded != 0) {
if (hasContent)
res += " ";
if (numAdded == 1)
res += "Added interface <code>" + addedInterfaces + "</code>.<br>";
else
res += "Added interfaces <code>" + addedInterfaces + "</code>.<br>";
hasContent = true;
}
if (res.compareTo("") == 0)
return null;
return res;
}
/** 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.io.*;
import java.util.*;
/* For SAX XML parsing */
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 a Comments from an XML file. The Comments object is the internal
* representation of the comments for the changes.
* All methods in this class for populating a Comments object are static.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class Comments {
/**
* All the possible comments known about, accessible by the commentID.
*/
public static Hashtable allPossibleComments = new Hashtable();
/** The old Comments object which is populated from the file read in. */
private static Comments oldComments_ = null;
/** Default constructor. */
public Comments() {
commentsList_ = new ArrayList(); // SingleComment[]
}
// The list of comments elements associated with this objects
public List commentsList_ = null; // SingleComment[]
/**
* 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.
*/
public static Comments readFile(String filename) {
// If validation is desired, write out the appropriate comments.xsd
// file in the same directory as the comments XML file.
if (XMLToAPI.validateXML) {
writeXSD(filename);
}
// If the file does not exist, return null
File f = new File(filename);
if (!f.exists())
return null;
// The instance of the Comments object which is populated from the file.
oldComments_ = new Comments();
try {
DefaultHandler handler = new CommentsHandler(oldComments_);
XMLReader parser = null;
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 (XMLToAPI.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);
}
Collections.sort(oldComments_.commentsList_);
return oldComments_;
} //readFile()
/**
* Write the XML Schema file used for validation.
*/
public static void writeXSD(String filename) {
String xsdFileName = filename;
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+1);
} else if (idx != -1 && idx2 == -1) {
xsdFileName = xsdFileName.substring(0, idx+1);
} else if (idx != -1 && idx2 != -1) {
int max = idx2 > idx ? idx2 : idx;
xsdFileName = xsdFileName.substring(0, max+1);
}
xsdFileName += "comments.xsd";
try {
FileOutputStream fos = new FileOutputStream(xsdFileName);
PrintWriter xsdFile = new PrintWriter(fos);
// The contents of the comments.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 comments.");
xsdFile.println(" </xsd:documentation>");
xsdFile.println("</xsd:annotation>");
xsdFile.println();
xsdFile.println("<xsd:element name=\"comments\" type=\"commentsType\"/>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"commentsType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"comment\" type=\"commentType\" minOccurs='0' 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=\"commentType\">");
xsdFile.println(" <xsd:sequence>");
xsdFile.println(" <xsd:element name=\"identifier\" type=\"identifierType\" minOccurs='1' maxOccurs='unbounded'/>");
xsdFile.println(" <xsd:element name=\"text\" type=\"xsd:string\" minOccurs='1' maxOccurs='1'/>");
xsdFile.println(" </xsd:sequence>");
xsdFile.println("</xsd:complexType>");
xsdFile.println();
xsdFile.println("<xsd:complexType name=\"identifierType\">");
xsdFile.println(" <xsd:attribute name=\"id\" 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);
}
}
//
// Methods to add data to a Comments object. Called by the XML parser and the
// report generator.
//
/**
* Add the SingleComment object to the list of comments kept by this
* object.
*/
public void addComment(SingleComment comment) {
commentsList_.add(comment);
}
//
// Methods to get data from a Comments object. Called by the report generator
//
/**
* The text placed into XML comments file where there is no comment yet.
* It never appears in reports.
*/
public static final String placeHolderText = "InsertCommentsHere";
/**
* Return the comment associated with the given id in the Comment object.
* If there is no such comment, return the placeHolderText.
*/
public static String getComment(Comments comments, String id) {
if (comments == null)
return placeHolderText;
SingleComment key = new SingleComment(id, null);
int idx = Collections.binarySearch(comments.commentsList_, key);
if (idx < 0) {
return placeHolderText;
} else {
int startIdx = comments.commentsList_.indexOf(key);
int endIdx = comments.commentsList_.indexOf(key);
int numIdx = endIdx - startIdx + 1;
if (numIdx != 1) {
System.out.println("Warning: " + numIdx + " identical ids in the existing comments file. Using the first instance.");
}
SingleComment singleComment = (SingleComment)(comments.commentsList_.get(idx));
// Convert @link tags to links
return singleComment.text_;
}
}
/**
* Convert @link tags to HTML links.
*/
public static String convertAtLinks(String text, String currentElement,
PackageAPI pkg, ClassAPI cls) {
if (text == null)
return null;
StringBuffer result = new StringBuffer();
int state = -1;
final int NORMAL_TEXT = -1;
final int IN_LINK = 1;
final int IN_LINK_IDENTIFIER = 2;
final int IN_LINK_IDENTIFIER_REFERENCE = 3;
final int IN_LINK_IDENTIFIER_REFERENCE_PARAMS = 6;
final int IN_LINK_LINKTEXT = 4;
final int END_OF_LINK = 5;
StringBuffer identifier = null;
StringBuffer identifierReference = null;
StringBuffer linkText = null;
// Figure out relative reference if required.
String ref = "";
if (currentElement.compareTo("class") == 0 ||
currentElement.compareTo("interface") == 0) {
ref = pkg.name_ + "." + cls.name_ + ".";
} else if (currentElement.compareTo("package") == 0) {
ref = pkg.name_ + ".";
}
ref = ref.replace('.', '/');
for (int i=0; i < text.length(); i++) {
char c = text.charAt(i);
char nextChar = i < text.length()-1 ? text.charAt(i+1) : (char)-1;
int remainingChars = text.length() - i;
switch (state) {
case NORMAL_TEXT:
if (c == '{' && remainingChars >= 6) {
if ("{@link".equals(text.substring(i, i + 6))) {
state = IN_LINK;
identifier = null;
identifierReference = null;
linkText = null;
i += 5;
continue;
}
}
result.append(c);
break;
case IN_LINK:
if (Character.isWhitespace(nextChar)) continue;
if (nextChar == '}') {
// End of the link
state = END_OF_LINK;
} else if (!Character.isWhitespace(nextChar)) {
state = IN_LINK_IDENTIFIER;
}
break;
case IN_LINK_IDENTIFIER:
if (identifier == null) {
identifier = new StringBuffer();
}
if (c == '#') {
// We have a reference.
state = IN_LINK_IDENTIFIER_REFERENCE;
// Don't append #
continue;
} else if (Character.isWhitespace(c)) {
// We hit some whitespace: the next character is the beginning
// of the link text.
state = IN_LINK_LINKTEXT;
continue;
}
identifier.append(c);
// Check for a } that ends the link.
if (nextChar == '}') {
state = END_OF_LINK;
}
break;
case IN_LINK_IDENTIFIER_REFERENCE:
if (identifierReference == null) {
identifierReference = new StringBuffer();
}
if (Character.isWhitespace(c)) {
state = IN_LINK_LINKTEXT;
continue;
}
identifierReference.append(c);
if (c == '(') {
state = IN_LINK_IDENTIFIER_REFERENCE_PARAMS;
}
if (nextChar == '}') {
state = END_OF_LINK;
}
break;
case IN_LINK_IDENTIFIER_REFERENCE_PARAMS:
// We're inside the parameters of a reference. Spaces are allowed.
if (c == ')') {
state = IN_LINK_IDENTIFIER_REFERENCE;
}
identifierReference.append(c);
if (nextChar == '}') {
state = END_OF_LINK;
}
break;
case IN_LINK_LINKTEXT:
if (linkText == null) linkText = new StringBuffer();
linkText.append(c);
if (nextChar == '}') {
state = END_OF_LINK;
}
break;
case END_OF_LINK:
if (identifier != null) {
result.append("<A HREF=\"");
result.append(HTMLReportGenerator.newDocPrefix);
result.append(ref);
result.append(identifier.toString().replace('.', '/'));
result.append(".html");
if (identifierReference != null) {
result.append("#");
result.append(identifierReference);
}
result.append("\">"); // target=_top?
result.append("<TT>");
if (linkText != null) {
result.append(linkText);
} else {
result.append(identifier);
if (identifierReference != null) {
result.append(".");
result.append(identifierReference);
}
}
result.append("</TT>");
result.append("</A>");
}
state = NORMAL_TEXT;
break;
}
}
return result.toString();
}
//
// Methods to write a Comments object out to a file.
//
/**
* Write the XML representation of comments to a file.
*
* @param outputFileName The name of the comments file.
* @param oldComments The old comments on the changed APIs.
* @param newComments The new comments on the changed APIs.
* @return true if no problems encountered
*/
public static boolean writeFile(String outputFileName,
Comments newComments) {
try {
FileOutputStream fos = new FileOutputStream(outputFileName);
outputFile = new PrintWriter(fos);
newComments.emitXMLHeader(outputFileName);
newComments.emitComments();
newComments.emitXMLFooter();
outputFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + outputFileName);
System.out.println("Error: "+ e.getMessage());
System.exit(1);
}
return true;
}
/**
* Write the Comments object out in XML.
*/
public void emitComments() {
Iterator iter = commentsList_.iterator();
while (iter.hasNext()) {
SingleComment currComment = (SingleComment)(iter.next());
if (!currComment.isUsed_)
outputFile.println("<!-- This comment is no longer used ");
outputFile.println("<comment>");
outputFile.println(" <identifier id=\"" + currComment.id_ + "\"/>");
outputFile.println(" <text>");
outputFile.println(" " + currComment.text_);
outputFile.println(" </text>");
outputFile.println("</comment>");
if (!currComment.isUsed_)
outputFile.println("-->");
}
}
/**
* Dump the contents of a Comments object out for inspection.
*/
public void dump() {
Iterator iter = commentsList_.iterator();
int i = 0;
while (iter.hasNext()) {
i++;
SingleComment currComment = (SingleComment)(iter.next());
System.out.println("Comment " + i);
System.out.println("id = " + currComment.id_);
System.out.println("text = \"" + currComment.text_ + "\"");
System.out.println("isUsed = " + currComment.isUsed_);
}
}
/**
* Emit messages about which comments are now unused and which are new.
*/
public static void noteDifferences(Comments oldComments, Comments newComments) {
if (oldComments == null) {
System.out.println("Note: all the comments have been newly generated");
return;
}
// See which comment ids are no longer used and add those entries to
// the new comments, marking them as unused.
Iterator iter = oldComments.commentsList_.iterator();
while (iter.hasNext()) {
SingleComment oldComment = (SingleComment)(iter.next());
int idx = Collections.binarySearch(newComments.commentsList_, oldComment);
if (idx < 0) {
System.out.println("Warning: comment \"" + oldComment.id_ + "\" is no longer used.");
oldComment.isUsed_ = false;
newComments.commentsList_.add(oldComment);
}
}
}
/**
* Emit the XML header.
*/
public void emitXMLHeader(String filename) {
outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
outputFile.println("<comments");
outputFile.println(" xmlns:xsi='" + RootDocToXML.baseURI + "/2001/XMLSchema-instance'");
outputFile.println(" xsi:noNamespaceSchemaLocation='comments.xsd'");
// Extract the identifier from the filename by removing the suffix
int idx = filename.lastIndexOf('.');
String apiIdentifier = filename.substring(0, idx);
// Also remove the output directory and directory separator if present
if (HTMLReportGenerator.outputDir != null)
apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.outputDir.length()+1);
// Also remove "user_comments_for_"
apiIdentifier = apiIdentifier.substring(18);
outputFile.println(" name=\"" + apiIdentifier + "\"");
outputFile.println(" jdversion=\"" + JDiff.version + "\">");
outputFile.println();
outputFile.println("<!-- This file contains comments for a JDiff report. -->");
outputFile.println("<!-- It is used only in generating the report, and does not need to ship with the final report. -->");
outputFile.println();
outputFile.println("<!-- The id attribute in an identifier element identifies the change as noted in the report. -->");
outputFile.println("<!-- An id has the form package[.class[.[ctor|method|field].signature]], where [] indicates optional text. -->");
outputFile.println("<!-- A comment element can have multiple identifier elements, which will -->");
outputFile.println("<!-- will cause the same text to appear at each place in the report, but -->");
outputFile.println("<!-- will be converted to separate comments when the comments file is used. -->");
outputFile.println("<!-- HTML tags in the text field will appear in the report. -->");
outputFile.println("<!-- You also need to close p HTML elements, used for paragraphs - see the top-level documentation. -->");
}
/**
* Emit the XML footer.
*/
public void emitXMLFooter() {
outputFile.println();
outputFile.println("</comments>");
}
private static List oldAPIList = null;
private static List newAPIList = null;
/**
* Return true if the given HTML tag has no separate </tag> end element.
*
* If you want to be able to use sloppy HTML in your comments, then you can
* add the element, e.g. li back into the condition here. However, if you
* then become more careful and do provide the closing tag, the output is
* generally just the closing tag, which is incorrect.
*
* tag.equalsIgnoreCase("tr") || // Is sometimes minimized
* tag.equalsIgnoreCase("th") || // Is sometimes minimized
* tag.equalsIgnoreCase("td") || // Is sometimes minimized
* tag.equalsIgnoreCase("dt") || // Is sometimes minimized
* tag.equalsIgnoreCase("dd") || // Is sometimes minimized
* tag.equalsIgnoreCase("img") || // Is sometimes minimized
* tag.equalsIgnoreCase("code") || // Is sometimes minimized (error)
* tag.equalsIgnoreCase("font") || // Is sometimes minimized (error)
* tag.equalsIgnoreCase("ul") || // Is sometimes minimized
* tag.equalsIgnoreCase("ol") || // Is sometimes minimized
* tag.equalsIgnoreCase("li") // Is sometimes minimized
*/
public static boolean isMinimizedTag(String tag) {
if (tag.equalsIgnoreCase("p") ||
tag.equalsIgnoreCase("br") ||
tag.equalsIgnoreCase("hr")
) {
return true;
}
return false;
}
/**
* The file where the XML representing the new Comments object is stored.
*/
private static PrintWriter outputFile = null;
}
package jdiff;
import java.io.*;
import java.util.*;
/* For SAX XML parsing */
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* Handle the parsing of an XML file and the generation of a Comments object.
*
* All HTML written for the comments sections in the report must
* use tags such as &lt;p/&gt; rather than just &lt;p&gt;, since the XML
* parser used requires that or matching end elements.
*
* From http://www.w3.org/TR/2000/REC-xhtml1-20000126:
* "Empty elements must either have an end tag or the start tag must end with /&lt;".
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class CommentsHandler extends DefaultHandler {
/** The Comments object which is populated from the XML file. */
public Comments comments_ = null;
/** The current SingleComment object being populated. */
private List currSingleComment_ = null; // SingleComment[]
/** Set if in text. */
private boolean inText = false;
/** The current text which is being assembled from chunks. */
private String currentText = null;
/** The stack of SingleComments still waiting for comment text. */
private LinkedList tagStack = null;
/** Default constructor. */
public CommentsHandler(Comments comments) {
comments_ = comments;
tagStack = new LinkedList();
}
public void startDocument() {
}
public void endDocument() {
if (trace)
comments_.dump();
}
public void startElement(java.lang.String uri, java.lang.String localName,
java.lang.String qName, Attributes attributes) {
// The change to JAXP compliance produced this change.
if (localName.equals(""))
localName = qName;
if (localName.compareTo("comments") == 0) {
String commentsName = attributes.getValue("name");
String version = attributes.getValue("jdversion"); // Not used yet
if (commentsName == null) {
System.out.println("Error: no identifier found in the comments XML file.");
System.exit(3);
}
// Check the given names against the names of the APIs
int idx1 = JDiff.oldFileName.lastIndexOf('.');
int idx2 = JDiff.newFileName.lastIndexOf('.');
String filename2 = JDiff.oldFileName.substring(0, idx1) +
"_to_" + JDiff.newFileName.substring(0, idx2);
if (filename2.compareTo(commentsName) != 0) {
System.out.println("Warning: API identifier in the comments XML file (" + filename2 + ") differs from the name of the file.");
}
} else if (localName.compareTo("comment") == 0) {
currSingleComment_ = new ArrayList(); // SingleComment[];
} else if (localName.compareTo("identifier") == 0) {
// May have multiple identifiers for one comment's text
String id = attributes.getValue("id");
SingleComment newComment = new SingleComment(id, null);
// Store it here until we can add text to it
currSingleComment_.add(newComment);
} else if (localName.compareTo("text") == 0) {
inText = true;
currentText = null;
} else {
if (inText) {
// Start of an element, probably an HTML element
addStartTagToText(localName, attributes);
} else {
System.out.println("Error: unknown element type: " + localName);
System.exit(-1);
}
}
}
public void endElement(java.lang.String uri, java.lang.String localName,
java.lang.String qName) {
if (localName.equals(""))
localName = qName;
if (localName.compareTo("text") == 0) {
inText = false;
addTextToComments();
} else if (inText) {
addEndTagToText(localName);
}
}
/** Deal with a chunk of text. The text may come in multiple chunks. */
public void characters(char[] ch, int start, int length) {
if (inText) {
String chunk = new String(ch, start, length);
if (currentText == null)
currentText = chunk;
else
currentText += chunk;
}
}
/**
* Trim the current text, check it is a sentence and add it to all
* the comments which are waiting for it.
*/
public void addTextToComments() {
// Eliminate any whitespace at each end of the text.
currentText = currentText.trim();
// Check that it is a sentence
if (!currentText.endsWith(".") &&
!currentText.endsWith("?") &&
!currentText.endsWith("!") &&
currentText.compareTo(Comments.placeHolderText) != 0) {
System.out.println("Warning: text of comment does not end in a period: " + currentText);
}
// Add this comment to all the SingleComments waiting for it
Iterator iter = currSingleComment_.iterator();
while (iter.hasNext()) {
SingleComment currComment = (SingleComment)(iter.next());
if (currComment.text_ == null)
currComment.text_ = currentText;
else
currComment.text_ += currentText;
comments_.addComment(currComment);
}
}
/**
* Add the start tag to the current comment text.
*/
public void addStartTagToText(String localName, Attributes attributes) {
// Need to insert the HTML tag into the current text
String currentHTMLTag = localName;
// Save the tag in a stack
tagStack.add(currentHTMLTag);
String tag = "<" + currentHTMLTag;
// Now add all the attributes into the current text
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
tag += " " + name + "=\"" + value+ "\"";
}
// End the tag
if (Comments.isMinimizedTag(currentHTMLTag)) {
tag += "/>";
} else {
tag += ">";
}
// Now insert the HTML tag into the current text
if (currentText == null)
currentText = tag;
else
currentText += tag;
}
/**
* Add the end tag to the current comment text.
*/
public void addEndTagToText(String localName) {
// Close the current HTML tag
String currentHTMLTag = (String)(tagStack.removeLast());
if (!Comments.isMinimizedTag(currentHTMLTag))
currentText += "</" + currentHTMLTag + ">";
}
public void warning(SAXParseException e) {
System.out.println("Warning (" + e.getLineNumber() + "): parsing XML comments file:" + e);
e.printStackTrace();
}
public void error(SAXParseException e) {
System.out.println("Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
e.printStackTrace();
System.exit(1);
}
public void fatalError(SAXParseException e) {
System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
e.printStackTrace();
System.exit(1);
}
/** Set to enable increased logging verbosity for debugging. */
private static final boolean trace = false;
}
package jdiff;
import java.util.*;
/**
* Class to compare two ClassDiff objects.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class CompareClassPdiffs implements Comparator {
/**
* Compare two class diffs by their percentage difference,
* and then by name.
*/
public int compare(Object obj1, Object obj2){
ClassDiff c1 = (ClassDiff)obj1;
ClassDiff c2 = (ClassDiff)obj2;
if (c1.pdiff < c2.pdiff)
return 1;
if (c1.pdiff > c2.pdiff)
return -1;
return c1.name_.compareTo(c2.name_);
}
}
package jdiff;
import java.util.*;
/**
* Class to compare two PackageDiff objects.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class ComparePkgPdiffs implements Comparator {
/**
* Compare two package diffs by their percentage difference,
* and then by name.
*/
public int compare(Object obj1, Object obj2){
PackageDiff p1 = (PackageDiff)obj1;
PackageDiff p2 = (PackageDiff)obj2;
if (p1.pdiff < p2.pdiff)
return 1;
if (p1.pdiff > p2.pdiff)
return -1;
return p1.name_.compareTo(p2.name_);
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent a constructor, analogous to ConstructorDoc in the
* Javadoc doclet API.
*
* The method used for Collection comparison (compareTo) must make its
* comparison based upon everything that is known about this constructor.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class ConstructorAPI implements Comparable {
/**
* The type of the constructor, being all the parameter types
* separated by commas.
*/
public String type_ = null;
/**
* The exceptions thrown by this constructor, being all the exception types
* separated by commas. "no exceptions" if no exceptions are thrown.
*/
public String exceptions_ = "no exceptions";
/** Modifiers for this class. */
public Modifiers modifiers_;
/** The doc block, default is null. */
public String doc_ = null;
/** Constructor. */
public ConstructorAPI(String type, Modifiers modifiers) {
type_ = type;
modifiers_ = modifiers;
}
/** Compare two ConstructorAPI objects by type and modifiers. */
public int compareTo(Object o) {
ConstructorAPI constructorAPI = (ConstructorAPI)o;
int comp = type_.compareTo(constructorAPI.type_);
if (comp != 0)
return comp;
comp = exceptions_.compareTo(constructorAPI.exceptions_);
if (comp != 0)
return comp;
comp = modifiers_.compareTo(constructorAPI.modifiers_);
if (comp != 0)
return comp;
if (APIComparator.docChanged(doc_, constructorAPI.doc_))
return -1;
return 0;
}
/**
* Tests two constructors, using just the type, used by indexOf().
*/
public boolean equals(Object o) {
if (type_.compareTo(((ConstructorAPI)o).type_) == 0)
return true;
return false;
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to generate colored differences between two sections of HTML text.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class Diff {
/**
* Save the differences between the two strings in a DiffOutput object
* for later use.
*
* @param id A per-package unique identifier for each documentation
* change.
*/
static String saveDocDiffs(String pkgName, String className,
String oldDoc, String newDoc,
String id, String title) {
// Generate the string which will link to this set of diffs
if (noDocDiffs)
return "Documentation changed from ";
if (oldDoc == null || newDoc == null) {
return "Documentation changed from ";
}
// Generate the differences.
generateDiffs(pkgName, className, oldDoc, newDoc, id, title);
return "Documentation <a href=\"" + diffFileName + pkgName +
HTMLReportGenerator.reportFileExt + "#" + id +
"\">changed</a> from ";
}
/**
* Generate the differences.
*/
static void generateDiffs(String pkgName, String className,
String oldDoc, String newDoc,
String id, String title) {
String[] oldDocWords = parseDoc(oldDoc);
String[] newDocWords = parseDoc(newDoc);
DiffMyers diff = new DiffMyers(oldDocWords, newDocWords);
DiffMyers.change script = diff.diff_2(false);
script = mergeDiffs(oldDocWords, newDocWords, script);
String text = "<A NAME=\"" + id + "\"></A>" + title + "<br><br>";
// Generate the differences in blockquotes to cope with unterminated
// HTML tags
text += "<blockquote>";
text = addDiffs(oldDocWords, newDocWords, script, text);
text += "</blockquote>";
docDiffs.add(new DiffOutput(pkgName, className, id, title, text));
}
/**
* Convert the string to an array of strings, but don't break HTML tags up.
*/
static String[] parseDoc(String doc) {
String delimiters = " .,;:?!(){}[]\"'~@#$%^&*+=_-|\\<>/";
StringTokenizer st = new StringTokenizer(doc, delimiters, true);
List docList = new ArrayList();
boolean inTag = false;
String tag = null;
while (st.hasMoreTokens()) {
String tok = st.nextToken();
if (!inTag) {
if (tok.compareTo("<") == 0) {
tag = tok;
if (st.hasMoreTokens()) {
// See if this really is a tag
tok = st.nextToken();
char ch = tok.charAt(0);
if (Character.isLetter(ch) || ch == '/') {
inTag = true;
tag += tok;
}
}
if (!inTag)
docList.add(tag);
} else {
docList.add(tok);
}
} else {
// Add all tokens to the tag until the closing > is seen
if (tok.compareTo(">") == 0) {
inTag = false;
tag += tok;
docList.add(tag);
} else {
tag += tok;
}
}
}
if (inTag) {
// An unterminated tag, or more likely, < used instead of &lt;
// There are no nested tags such as <a <b>> in HTML
docList.add(tag);
}
String[] docWords = new String[docList.size()];
docWords = (String[])docList.toArray(docWords);
return docWords;
}
/**
* For improved readability, merge changes of the form
* "delete 1, insert 1, space, delete 1, insert 1"
* to
* "delete 3, insert 3" (including the space).
*
* @param oldDocWords The original documentation as a String array
* @param newDocWords The new documentation as a String array
*/
static DiffMyers.change mergeDiffs(String[] oldDocWords, String[] newDocWords,
DiffMyers.change script) {
if (script.link == null)
return script; // Only one change
DiffMyers.change hunk = script;
DiffMyers.change lasthunk = null; // Set to the last potential hunk
int startOld = 0;
for (; hunk != null; hunk = hunk.link) {
int deletes = hunk.deleted;
int inserts = hunk.inserted;
if (lasthunk == null) {
if (deletes == 1 && inserts == 1) {
// This is the start of a potential merge
lasthunk = hunk;
}
continue;
} else {
int first0 = hunk.line0; // Index of first deleted word
int first1 = hunk.line1; // Index of first inserted word
if (deletes == 1 && inserts == 1 &&
oldDocWords[first0 - 1].compareTo(" ") == 0 &&
newDocWords[first1 - 1].compareTo(" ") == 0 &&
first0 == lasthunk.line0 + lasthunk.deleted + 1 &&
first1 == lasthunk.line1 + lasthunk.inserted + 1) {
// Merge this change into the last change
lasthunk.deleted += 2;
lasthunk.inserted += 2;
lasthunk.link = hunk.link;
} else {
lasthunk = null;
}
}
}
return script;
}
/**
* Add the differences to the text passed in. The old documentation is
* edited using the edit script provided by the DiffMyers object.
* Do not display diffs in HTML tags.
*
* @param oldDocWords The original documentation as a String array
* @param newDocWords The new documentation as a String array
* @return The text for this documentation difference
*/
static String addDiffs(String[] oldDocWords, String[] newDocWords,
DiffMyers.change script, String text) {
String res = text;
DiffMyers.change hunk = script;
int startOld = 0;
if (trace) {
System.out.println("Old Text:");
for (int i = 0; i < oldDocWords.length; i++) {
System.out.print(oldDocWords[i]);
}
System.out.println(":END");
System.out.println("New Text:");
for (int i = 0; i < newDocWords.length; i++) {
System.out.print(newDocWords[i]);
}
System.out.println(":END");
}
for (; hunk != null; hunk = hunk.link) {
int deletes = hunk.deleted;
int inserts = hunk.inserted;
if (deletes == 0 && inserts == 0) {
continue; // Not clear how this would occur, but handle it
}
// Determine the range of word and delimiter numbers involved
// in each file.
int first0 = hunk.line0; // Index of first deleted word
// Index of last deleted word, invalid if deletes == 0
int last0 = hunk.line0 + hunk.deleted - 1;
int first1 = hunk.line1; // Index of first inserted word
// Index of last inserted word, invalid if inserts == 0
int last1 = hunk.line1 + hunk.inserted - 1;
if (trace) {
System.out.println("HUNK: ");
System.out.println("inserts: " + inserts);
System.out.println("deletes: " + deletes);
System.out.println("first0: " + first0);
System.out.println("last0: " + last0);
System.out.println("first1: " + first1);
System.out.println("last1: " + last1);
}
// Emit the original document up to this change
for (int i = startOld; i < first0; i++) {
res += oldDocWords[i];
}
// Record where to start the next hunk from
startOld = last0 + 1;
// Emit the deleted words, but struck through
// but do not emit deleted HTML tags
if (deletes != 0) {
boolean inStrike = false;
for (int i = first0; i <= last0; i++) {
if (!oldDocWords[i].startsWith("<") &&
!oldDocWords[i].endsWith(">")) {
if (!inStrike) {
if (deleteEffect == 0)
res += "<strike>";
else if (deleteEffect == 1)
res += "<span style=\"background: #FFCCCC\">";
inStrike = true;
}
res += oldDocWords[i];
}
}
if (inStrike) {
if (deleteEffect == 0)
res += "</strike>";
else if (deleteEffect == 1)
res += "</span>";
}
}
// Emit the inserted words, but do not emphasise new HTML tags
if (inserts != 0) {
boolean inEmph = false;
for (int i = first1; i <= last1; i++) {
if (!newDocWords[i].startsWith("<") &&
!newDocWords[i].endsWith(">")) {
if (!inEmph) {
if (insertEffect == 0)
res += "<font color=\"red\">";
else if (insertEffect == 1)
res += "<span style=\"background: #FFFF00\">";
inEmph = true;
}
}
res += newDocWords[i];
}
if (inEmph) {
if (insertEffect == 0)
res += "</font>";
else if (insertEffect == 1)
res += "</span>";
}
}
} //for (; hunk != null; hunk = hunk.link)
// Print out the remaining part of the old text
for (int i = startOld; i < oldDocWords.length; i++) {
res += oldDocWords[i];
}
return res;
}
/**
* Emit all the documentation differences into one file per package.
*/
static void emitDocDiffs(String fullReportFileName) {
Collections.sort(docDiffs);
DiffOutput[] docDiffsArr = new DiffOutput[docDiffs.size()];
docDiffsArr = (DiffOutput[])docDiffs.toArray(docDiffsArr);
for (int i = 0; i < docDiffsArr.length; i++) {
DiffOutput diffOutput = docDiffsArr[i];
if (currPkgName == null ||
currPkgName.compareTo(diffOutput.pkgName_) != 0) {
// Open a different file for each package, add the HTML header,
// the navigation bar and some preamble.
if (currPkgName != null)
closeDiffFile(); // Close the existing file
// Create the HTML link to the previous package
String prevPkgName = currPkgName;
if (currPkgName != null) {
prevPkgName = diffFileName + docDiffsArr[i-1].pkgName_ +
HTMLReportGenerator.reportFileExt;
}
// Set the current package name
currPkgName = diffOutput.pkgName_;
// Create the HTML link to the next package
String nextPkgName = null;
for (int j = i; j < docDiffsArr.length; j++) {
if (currPkgName.compareTo(docDiffsArr[j].pkgName_) != 0) {
nextPkgName = diffFileName + docDiffsArr[j].pkgName_ +
HTMLReportGenerator.reportFileExt;
break;
}
}
String fullDiffFileName = fullReportFileName +
JDiff.DIR_SEP + diffFileName + currPkgName +
HTMLReportGenerator.reportFileExt;
// Create the output file
try {
FileOutputStream fos = new FileOutputStream(fullDiffFileName);
diffFile = new PrintWriter(fos);
// Write the HTML header
diffFile.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
diffFile.println("<HTML>");
diffFile.println("<HEAD>");
diffFile.println("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
diffFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
diffFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
// diffFile.println("<!-- on " + new Date() + " -->");
diffFile.println("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
diffFile.println("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
diffFile.println("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"" + "../" + "stylesheet-jdiff.css\" TITLE=\"Style\">");
diffFile.println("<TITLE>");
diffFile.println(currPkgName + " Documentation Differences");
diffFile.println("</TITLE>");
diffFile.println("</HEAD>");
diffFile.println("<BODY>");
// Write the navigation bar
diffFile.println("<!-- Start of nav bar -->");
diffFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
diffFile.println("<TR>");
diffFile.println("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
diffFile.println(" <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
diffFile.println(" <TR ALIGN=\"center\" VALIGN=\"top\">");
// Always have a link to the Javadoc files
String pkgRef = currPkgName;
pkgRef = pkgRef.replace('.', '/');
pkgRef = HTMLReportGenerator.newDocPrefix + pkgRef + "/package-summary";
diffFile.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>");
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.reportFileName + "-summary" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
diffFile.println(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
if (!Diff.noDocDiffs) {
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
}
if (HTMLReportGenerator.doStats) {
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
}
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
diffFile.println(" </TR>");
diffFile.println(" </TABLE>");
diffFile.println("</TD>");
// The right hand side title
diffFile.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>");
diffFile.println("</TR>");
// Links for previous and next, and frames and no frames
diffFile.println("<TR>");
diffFile.println(" <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
if (prevPkgName != null)
diffFile.println(" <A HREF=\"" + prevPkgName + "\"><B>PREV PACKAGE</B></A> &nbsp;");
else
diffFile.println(" <B>PREV PACKAGE</B> &nbsp;");
if (nextPkgName != null)
diffFile.println(" &nbsp;<A HREF=\"" + nextPkgName + "\"><B>NEXT PACKAGE</B></A>");
else
diffFile.println(" &nbsp;<B>NEXT PACKAGE</B>");
diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
diffFile.println(" <A HREF=\"" + "../" + HTMLReportGenerator.reportFileName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A> &nbsp;");
diffFile.println(" &nbsp;<A HREF=\"" + diffFileName + currPkgName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
diffFile.println(" <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\">&nbsp;</TD>");
diffFile.println("</TR>");
diffFile.println("</TABLE>");
diffFile.println("<HR>");
diffFile.println("<!-- End of nav bar -->");
diffFile.println("<h2>");
diffFile.println(currPkgName + " Documentation Differences");
diffFile.println("</h2>");
diffFile.println();
diffFile.println("<blockquote>");
diffFile.println("This file contains all the changes in documentation in the package <code>" + currPkgName + "</code> as colored differences.");
if (deleteEffect == 0)
diffFile.println("Deletions are shown <strike>like this</strike>, and");
else if (deleteEffect == 1)
diffFile.println("Deletions are shown <span style=\"background: #FFCCCC\">like this</span>, and");
if (insertEffect == 0)
diffFile.println("additions are shown in red <font color=\"red\">like this</font>.");
else if (insertEffect == 1)
diffFile.println("additions are shown <span style=\"background: #FFFF00\">like this</span>.");
diffFile.println("</blockquote>");
diffFile.println("<blockquote>");
diffFile.println("If no deletions or additions are shown in an entry, the HTML tags will be what has changed. The <i>new</i> HTML tags are shown in the differences. ");
diffFile.println("If no documentation existed, and then some was added in a later version, this change is noted in the appropriate class pages of differences, but the change is not shown on this page. Only changes in existing text are shown here. ");
diffFile.println("Similarly, documentation which was inherited from another class or interface is not shown here.");
diffFile.println("</blockquote>");
diffFile.println("<blockquote>");
diffFile.println(" Note that an HTML error in the new documentation may cause the display of other documentation changes to be presented incorrectly. For instance, failure to close a &lt;code&gt; tag will cause all subsequent paragraphs to be displayed differently.");
diffFile.println("</blockquote>");
diffFile.println("<hr>");
diffFile.println();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + fullDiffFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
} // if (currPkgName == null || currPkgName.compareTo(diffOutput.pkgName_) != 0)
// Now add the documentation difference text
diffFile.println(diffOutput.text_);
// Separate with a horizontal line
if (i != docDiffsArr.length - 1 &&
diffOutput.className_ != null &&
docDiffsArr[i+1].className_ != null &&
diffOutput.className_.compareTo(docDiffsArr[i+1].className_) != 0)
diffFile.println("<hr align=\"left\" width=\"100%\">");
// else
// diffFile.println("<hr align=\"left\" width=\"50%\">");
} // for (i = 0;
if (currPkgName != null)
closeDiffFile(); // Close the existing file
// Emit the single file which is the index to all documentation changes
emitDocDiffIndex(fullReportFileName, docDiffsArr);
}
/**
* Emit the single file which is the index to all documentation changes.
*/
public static void emitDocDiffIndex(String fullReportFileName,
DiffOutput[] docDiffsArr) {
String fullDiffFileName = fullReportFileName +
JDiff.DIR_SEP + diffFileName + "index" +
HTMLReportGenerator.reportFileExt;
// Create the output file
try {
FileOutputStream fos = new FileOutputStream(fullDiffFileName);
diffFile = new PrintWriter(fos);
// Write the HTML header
diffFile.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
diffFile.println("<HTML>");
diffFile.println("<HEAD>");
diffFile.println("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
diffFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
diffFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
// diffFile.println("<!-- on " + new Date() + " -->");
diffFile.println("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
diffFile.println("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
diffFile.println("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"" + "../" + "stylesheet-jdiff.css\" TITLE=\"Style\">");
diffFile.println("<TITLE>");
diffFile.println("All Documentation Differences");
diffFile.println("</TITLE>");
diffFile.println("</HEAD>");
diffFile.println("<BODY>");
// Write the navigation bar
diffFile.println("<!-- Start of nav bar -->");
diffFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
diffFile.println("<TR>");
diffFile.println("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
diffFile.println(" <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
diffFile.println(" <TR ALIGN=\"center\" VALIGN=\"top\">");
// Always have a link to the Javadoc files
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + APIDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.reportFileName + "-summary" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
diffFile.println(" <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
if (!Diff.noDocDiffs) {
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> <FONT CLASS=\"NavBarFont1Rev\"><B>Text Changes</B></FONT>&nbsp;</TD>");
}
if (HTMLReportGenerator.doStats) {
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
}
diffFile.println(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
diffFile.println(" </TR>");
diffFile.println(" </TABLE>");
diffFile.println("</TD>");
// The right hand side title
diffFile.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>");
diffFile.println("</TR>");
// Links for frames and no frames
diffFile.println("<TR>");
diffFile.println(" <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
diffFile.println(" <A HREF=\"" + "../" + HTMLReportGenerator.reportFileName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A> &nbsp;");
diffFile.println(" &nbsp;<A HREF=\"" + diffFileName + "index" + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
diffFile.println(" <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\">&nbsp;</TD>");
diffFile.println("</TR>");
diffFile.println("</TABLE>");
diffFile.println("<HR>");
diffFile.println("<!-- End of nav bar -->");
diffFile.println("<h2>");
diffFile.println("All Documentation Differences");
diffFile.println("</h2>");
diffFile.println();
// For each package and class, add the first DiffOutput to
// the hash table. Used when generating navigation bars.
boolean firstPackage = true; // Set for the first package
boolean firstClass = true; // Set for first class in a package
boolean firstCtor = true; // Set for first ctor in a class
boolean firstMethod = true; // Set for first method in a class
boolean firstField = true; // Set for first field in a class
for (int i = 0; i < docDiffsArr.length; i++) {
DiffOutput diffOutput = docDiffsArr[i];
String link = "<a href=\"" + Diff.diffFileName + diffOutput.pkgName_ + HTMLReportGenerator.reportFileExt + "#" + diffOutput.id_ + "\">";
// See if the package name changed
if (firstPackage || diffOutput.pkgName_.compareTo(docDiffsArr[i-1].pkgName_) != 0) {
if (firstPackage) {
firstPackage = false;
} else {
diffFile.println("<br>");
}
firstClass = true;
firstCtor = true;
firstMethod = true;
firstField = true;
String id = diffOutput.pkgName_ + "!package";
firstDiffOutput.put(id, id);
if (diffOutput.className_ == null) {
diffFile.println("<A NAME=\"" + id + "\"></A>" + link + "Package <b>" + diffOutput.pkgName_ + "</b></a><br>");
} else {
diffFile.println("<A NAME=\"" + id + "\"></A>" + "Package <b>" + diffOutput.pkgName_ + "</b><br>");
}
}
// See if the class name changed
if (diffOutput.className_ != null &&
(firstClass ||
diffOutput.className_.compareTo(docDiffsArr[i-1].className_) != 0)) {
if (firstClass) {
firstClass = false;
} else {
diffFile.println("<br>");
}
firstCtor = true;
firstMethod = true;
firstField = true;
String id = diffOutput.pkgName_ + "." + diffOutput.className_ + "!class";
firstDiffOutput.put(id, id);
if (diffOutput.id_.endsWith("!class")) {
diffFile.println("<A NAME=\"" + id + "\"></A>&nbsp;&nbsp;Class " + link + diffOutput.className_ + "</a><br>");
} else {
diffFile.println("<A NAME=\"" + id + "\"></A>&nbsp;&nbsp;Class " + diffOutput.className_ + "<br>");
}
}
// Work out what kind of member this is, and
// display it appropriately
if (diffOutput.className_ != null &&
!diffOutput.id_.endsWith("!class")) {
int ctorIdx = diffOutput.id_.indexOf(".ctor");
if (ctorIdx != -1) {
diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;" + link + diffOutput.className_ + diffOutput.id_.substring(ctorIdx + 5) + "</a><br>");
} else {
int methodIdx = diffOutput.id_.indexOf(".dmethod.");
if (methodIdx != -1) {
diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;" + "Method " + link + diffOutput.id_.substring(methodIdx + 9) + "</a><br>");
} else {
int fieldIdx = diffOutput.id_.indexOf(".field.");
if (fieldIdx != -1) {
diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;" + "Field " + link + diffOutput.id_.substring(fieldIdx + 7) + "</a><br>");
}
} //if (methodIdx != -1)
} //if (ctorIdx != -1)
} //diffOutput.className_ != null
}
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + fullDiffFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
closeDiffFile();
}
/**
* Emit the HTML footer and close the diff file.
*/
public static void closeDiffFile() {
if (diffFile != null) {
// Write the HTML footer
diffFile.println();
diffFile.println("</BODY>");
diffFile.println("</HTML>");
diffFile.close();
}
}
/**
* Current file where documentation differences are written as colored
* differences.
*/
public static PrintWriter diffFile = null;
/**
* Base name of the current file where documentation differences are
* written as colored differences.
*/
public static String diffFileName = "docdiffs_";
/**
* The name of the current package, used to create diffFileName.
*/
private static String currPkgName = null;
/**
* If set, then do not generate colored diffs for documentation.
* Default is true.
*/
public static boolean noDocDiffs = true;
/**
* Define the type of emphasis for deleted words.
* 0 strikes the words through.
* 1 outlines the words in light grey.
*/
public static int deleteEffect = 0;
/**
* Define the type of emphasis for inserted words.
* 0 colors the words red.
* 1 outlines the words in yellow, like a highlighter.
*/
public static int insertEffect = 1;
/**
* For each package and class, the first DiffOutput is added to
* this hash table. Used when generating navigation bars.
*/
public static Hashtable firstDiffOutput = new Hashtable();
/**
* If set, then show changes in implementation-related modifiers such as
* native and synchronized. For more information, see
* http://java.sun.com/j2se/1.4.1/docs/tooldocs/solaris/javadoc.html#generatedapideclarations
*/
public static boolean showAllChanges = false;
/** The list of documentation differences. */
private static List docDiffs = new ArrayList(); // DiffOutput[]
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
}
package jdiff;
import java.io.*;
import java.util.*;
/** A class to compare vectors of objects. The result of comparison
is a list of <code>change</code> objects which form an
edit script. The objects compared are traditionally lines
of text from two files. Comparison options such as "ignore
whitespace" are implemented by modifying the <code>equals</code>
and <code>hashcode</code> methods for the objects compared.
<p>
The basic algorithm is described in: </br>
"An O(ND) Difference Algorithm and its Variations", Eugene Myers,
Algorithmica Vol. 1 No. 2, 1986, p 251.
<p>
This class outputs different results from GNU diff 1.15 on some
inputs. Our results are actually better (smaller change list, smaller
total size of changes), but it would be nice to know why. Perhaps
there is a memory overwrite bug in GNU diff 1.15.
@author Stuart D. Gathman, translated from GNU diff 1.15
Copyright (C) 2000 Business Management Systems, Inc.
<p>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
<p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
<p>
You should have received a copy of the <a href=COPYING.txt>
GNU General Public License</a>
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
public class DiffMyers
{
/** Prepare to find differences between two arrays. Each element of
the arrays is translated to an "equivalence number" based on
the result of <code>equals</code>. The original Object arrays
are no longer needed for computing the differences. They will
be needed again later to print the results of the comparison as
an edit script, if desired.
*/
public DiffMyers(Object[] a,Object[] b)
{
Hashtable h = new Hashtable(a.length + b.length);
filevec[0] = new file_data(a,h);
filevec[1] = new file_data(b,h);
}
/** 1 more than the maximum equivalence value used for this or its
sibling file. */
private int equiv_max = 1;
/** When set to true, the comparison uses a heuristic to speed it up.
With this heuristic, for files with a constant small density
of changes, the algorithm is linear in the file size. */
public boolean heuristic = false;
/** When set to true, the algorithm returns a guarranteed minimal
set of changes. This makes things slower, sometimes much slower. */
public boolean no_discards = false;
private int[] xvec, yvec; /* Vectors being compared. */
private int[] fdiag; /* Vector, indexed by diagonal, containing
the X coordinate of the point furthest
along the given diagonal in the forward
search of the edit matrix. */
private int[] bdiag; /* Vector, indexed by diagonal, containing
the X coordinate of the point furthest
along the given diagonal in the backward
search of the edit matrix. */
private int fdiagoff, bdiagoff;
private final file_data[] filevec = new file_data[2];
private int cost;
/** Find the midpoint of the shortest edit script for a specified
portion of the two files.
We scan from the beginnings of the files, and simultaneously from the ends,
doing a breadth-first search through the space of edit-sequence.
When the two searches meet, we have found the midpoint of the shortest
edit sequence.
The value returned is the number of the diagonal on which the midpoint lies.
The diagonal number equals the number of inserted lines minus the number
of deleted lines (counting only lines before the midpoint).
The edit cost is stored into COST; this is the total number of
lines inserted or deleted (counting only lines before the midpoint).
This function assumes that the first lines of the specified portions
of the two files do not match, and likewise that the last lines do not
match. The caller must trim matching lines from the beginning and end
of the portions it is going to specify.
Note that if we return the "wrong" diagonal value, or if
the value of bdiag at that diagonal is "wrong",
the worst this can do is cause suboptimal diff output.
It cannot cause incorrect diff output. */
private int diag (int xoff, int xlim, int yoff, int ylim)
{
final int[] fd = fdiag; // Give the compiler a chance.
final int[] bd = bdiag; // Additional help for the compiler.
final int[] xv = xvec; // Still more help for the compiler.
final int[] yv = yvec; // And more and more . . .
final int dmin = xoff - ylim; // Minimum valid diagonal.
final int dmax = xlim - yoff; // Maximum valid diagonal.
final int fmid = xoff - yoff; // Center diagonal of top-down search.
final int bmid = xlim - ylim; // Center diagonal of bottom-up search.
int fmin = fmid, fmax = fmid; // Limits of top-down search.
int bmin = bmid, bmax = bmid; // Limits of bottom-up search.
/* True if southeast corner is on an odd
diagonal with respect to the northwest. */
final boolean odd = (fmid - bmid & 1) != 0;
fd[fdiagoff + fmid] = xoff;
bd[bdiagoff + bmid] = xlim;
for (int c = 1;; ++c)
{
int d; /* Active diagonal. */
boolean big_snake = false;
/* Extend the top-down search by an edit step in each diagonal. */
if (fmin > dmin)
fd[fdiagoff + --fmin - 1] = -1;
else
++fmin;
if (fmax < dmax)
fd[fdiagoff + ++fmax + 1] = -1;
else
--fmax;
for (d = fmax; d >= fmin; d -= 2)
{
int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];
if (tlo >= thi)
x = tlo + 1;
else
x = thi;
oldx = x;
y = x - d;
while (x < xlim && y < ylim && xv[x] == yv[y]) {
++x; ++y;
}
if (x - oldx > 20)
big_snake = true;
fd[fdiagoff + d] = x;
if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
{
cost = 2 * c - 1;
return d;
}
}
/* Similar extend the bottom-up search. */
if (bmin > dmin)
bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
else
++bmin;
if (bmax < dmax)
bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
else
--bmax;
for (d = bmax; d >= bmin; d -= 2)
{
int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];
if (tlo < thi)
x = tlo;
else
x = thi - 1;
oldx = x;
y = x - d;
while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
--x; --y;
}
if (oldx - x > 20)
big_snake = true;
bd[bdiagoff + d] = x;
if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
{
cost = 2 * c;
return d;
}
}
/* Heuristic: check occasionally for a diagonal that has made
lots of progress compared with the edit distance.
If we have any such, find the one that has made the most
progress and return it as if it had succeeded.
With this heuristic, for files with a constant small density
of changes, the algorithm is linear in the file size. */
if (c > 200 && big_snake && heuristic)
{
int best = 0;
int bestpos = -1;
for (d = fmax; d >= fmin; d -= 2)
{
int dd = d - fmid;
if ((fd[fdiagoff + d] - xoff)*2 - dd > 12 * (c + (dd > 0 ? dd : -dd)))
{
if (fd[fdiagoff + d] * 2 - dd > best
&& fd[fdiagoff + d] - xoff > 20
&& fd[fdiagoff + d] - d - yoff > 20)
{
int k;
int x = fd[fdiagoff + d];
/* We have a good enough best diagonal;
now insist that it end with a significant snake. */
for (k = 1; k <= 20; k++)
if (xvec[x - k] != yvec[x - d - k])
break;
if (k == 21)
{
best = fd[fdiagoff + d] * 2 - dd;
bestpos = d;
}
}
}
}
if (best > 0)
{
cost = 2 * c - 1;
return bestpos;
}
best = 0;
for (d = bmax; d >= bmin; d -= 2)
{
int dd = d - bmid;
if ((xlim - bd[bdiagoff + d])*2 + dd > 12 * (c + (dd > 0 ? dd : -dd)))
{
if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
&& xlim - bd[bdiagoff + d] > 20
&& ylim - (bd[bdiagoff + d] - d) > 20)
{
/* We have a good enough best diagonal;
now insist that it end with a significant snake. */
int k;
int x = bd[bdiagoff + d];
for (k = 0; k < 20; k++)
if (xvec[x + k] != yvec[x - d + k])
break;
if (k == 20)
{
best = (xlim - bd[bdiagoff + d]) * 2 + dd;
bestpos = d;
}
}
}
}
if (best > 0)
{
cost = 2 * c - 1;
return bestpos;
}
}
}
}
/** Compare in detail contiguous subsequences of the two files
which are known, as a whole, to match each other.
The results are recorded in the vectors filevec[N].changed_flag, by
storing a 1 in the element for each line that is an insertion or deletion.
The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
Note that XLIM, YLIM are exclusive bounds.
All line numbers are origin-0 and discarded lines are not counted. */
private void compareseq (int xoff, int xlim, int yoff, int ylim) {
/* Slide down the bottom initial diagonal. */
while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
++xoff; ++yoff;
}
/* Slide up the top initial diagonal. */
while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
--xlim; --ylim;
}
/* Handle simple cases. */
if (xoff == xlim)
while (yoff < ylim)
filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true;
else if (yoff == ylim)
while (xoff < xlim)
filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true;
else
{
/* Find a point of correspondence in the middle of the files. */
int d = diag (xoff, xlim, yoff, ylim);
int c = cost;
int f = fdiag[fdiagoff + d];
int b = bdiag[bdiagoff + d];
if (c == 1)
{
/* This should be impossible, because it implies that
one of the two subsequences is empty,
and that case was handled above without calling `diag'.
Let's verify that this is true. */
throw new IllegalArgumentException("Empty subsequence");
}
else
{
/* Use that point to split this problem into two subproblems. */
compareseq (xoff, b, yoff, b - d);
/* This used to use f instead of b,
but that is incorrect!
It is not necessarily the case that diagonal d
has a snake from b to f. */
compareseq (b, xlim, b - d, ylim);
}
}
}
/** Discard lines from one file that have no matches in the other file.
*/
private void discard_confusing_lines() {
filevec[0].discard_confusing_lines(filevec[1]);
filevec[1].discard_confusing_lines(filevec[0]);
}
private boolean inhibit = false;
/** Adjust inserts/deletes of blank lines to join changes
as much as possible.
*/
private void shift_boundaries() {
if (inhibit)
return;
filevec[0].shift_boundaries(filevec[1]);
filevec[1].shift_boundaries(filevec[0]);
}
/** Scan the tables of which lines are inserted and deleted,
producing an edit script in reverse order. */
private change build_reverse_script() {
change script = null;
final boolean[] changed0 = filevec[0].changed_flag;
final boolean[] changed1 = filevec[1].changed_flag;
final int len0 = filevec[0].buffered_lines;
final int len1 = filevec[1].buffered_lines;
/* Note that changedN[len0] does exist, and contains 0. */
int i0 = 0, i1 = 0;
while (i0 < len0 || i1 < len1)
{
if (changed0[1+i0] || changed1[1+i1])
{
int line0 = i0, line1 = i1;
/* Find # lines changed here in each file. */
while (changed0[1+i0]) ++i0;
while (changed1[1+i1]) ++i1;
/* Record this change. */
script = new change(line0, line1, i0 - line0, i1 - line1, script);
}
/* We have reached lines in the two files that match each other. */
i0++; i1++;
}
return script;
}
/** Scan the tables of which lines are inserted and deleted,
producing an edit script in forward order. */
private change build_script() {
change script = null;
final boolean[] changed0 = filevec[0].changed_flag;
final boolean[] changed1 = filevec[1].changed_flag;
final int len0 = filevec[0].buffered_lines;
final int len1 = filevec[1].buffered_lines;
int i0 = len0, i1 = len1;
/* Note that changedN[-1] does exist, and contains 0. */
while (i0 >= 0 || i1 >= 0)
{
if (changed0[i0] || changed1[i1])
{
int line0 = i0, line1 = i1;
/* Find # lines changed here in each file. */
while (changed0[i0]) --i0;
while (changed1[i1]) --i1;
/* Record this change. */
script = new change(i0, i1, line0 - i0, line1 - i1, script);
}
/* We have reached lines in the two files that match each other. */
i0--; i1--;
}
return script;
}
/* Report the differences of two files. DEPTH is the current directory
depth. */
public change diff_2(final boolean reverse) {
/* Some lines are obviously insertions or deletions
because they don't match anything. Detect them now,
and avoid even thinking about them in the main comparison algorithm. */
discard_confusing_lines ();
/* Now do the main comparison algorithm, considering just the
undiscarded lines. */
xvec = filevec[0].undiscarded;
yvec = filevec[1].undiscarded;
int diags =
filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
fdiag = new int[diags];
fdiagoff = filevec[1].nondiscarded_lines + 1;
bdiag = new int[diags];
bdiagoff = filevec[1].nondiscarded_lines + 1;
compareseq (0, filevec[0].nondiscarded_lines,
0, filevec[1].nondiscarded_lines);
fdiag = null;
bdiag = null;
/* Modify the results slightly to make them prettier
in cases where that can validly be done. */
shift_boundaries ();
/* Get the results of comparison in the form of a chain
of `struct change's -- an edit script. */
if (reverse)
return build_reverse_script();
else
return build_script();
}
/** The result of comparison is an "edit script": a chain of change objects.
Each change represents one place where some lines are deleted
and some are inserted.
LINE0 and LINE1 are the first affected lines in the two files (origin 0).
DELETED is the number of lines deleted here from file 0.
INSERTED is the number of lines inserted here in file 1.
If DELETED is 0 then LINE0 is the number of the line before
which the insertion was done; vice versa for INSERTED and LINE1. */
public static class change {
/** Previous or next edit command. */
public change link;
/** # lines of file 1 changed here. */
public int inserted;
/** # lines of file 0 changed here. */
public int deleted;
/** Line number of 1st deleted line. */
public final int line0;
/** Line number of 1st inserted line. */
public final int line1;
/** Cons an additional entry onto the front of an edit script OLD.
LINE0 and LINE1 are the first affected lines in the two files (origin 0).
DELETED is the number of lines deleted here from file 0.
INSERTED is the number of lines inserted here in file 1.
If DELETED is 0 then LINE0 is the number of the line before
which the insertion was done; vice versa for INSERTED and LINE1. */
change(int line0, int line1, int deleted, int inserted, change old) {
this.line0 = line0;
this.line1 = line1;
this.inserted = inserted;
this.deleted = deleted;
this.link = old;
//System.err.println(line0+","+line1+","+inserted+","+deleted);
}
}
/** Data on one input file being compared.
*/
class file_data {
/** Allocate changed array for the results of comparison. */
void clear() {
/* Allocate a flag for each line of each file, saying whether that line
is an insertion or deletion.
Allocate an extra element, always zero, at each end of each vector.
*/
changed_flag = new boolean[buffered_lines + 2];
}
/** Return equiv_count[I] as the number of lines in this file
that fall in equivalence class I.
@return the array of equivalence class counts.
*/
int[] equivCount() {
int[] equiv_count = new int[equiv_max];
for (int i = 0; i < buffered_lines; ++i)
++equiv_count[equivs[i]];
return equiv_count;
}
/** Discard lines that have no matches in another file.
A line which is discarded will not be considered by the actual
comparison algorithm; it will be as if that line were not in the file.
The file's `realindexes' table maps virtual line numbers
(which don't count the discarded lines) into real line numbers;
this is how the actual comparison algorithm produces results
that are comprehensible when the discarded lines are counted.
<p>
When we discard a line, we also mark it as a deletion or insertion
so that it will be printed in the output.
@param f the other file
*/
void discard_confusing_lines(file_data f) {
clear();
/* Set up table of which lines are going to be discarded. */
final byte[] discarded = discardable(f.equivCount());
/* Don't really discard the provisional lines except when they occur
in a run of discardables, with nonprovisionals at the beginning
and end. */
filterDiscards(discarded);
/* Actually discard the lines. */
discard(discarded);
}
/** Mark to be discarded each line that matches no line of another file.
If a line matches many lines, mark it as provisionally discardable.
@see equivCount()
@param counts The count of each equivalence number for the other file.
@return 0=nondiscardable, 1=discardable or 2=provisionally discardable
for each line
*/
private byte[] discardable(final int[] counts) {
final int end = buffered_lines;
final byte[] discards = new byte[end];
final int[] equivs = this.equivs;
int many = 5;
int tem = end / 64;
/* Multiply MANY by approximate square root of number of lines.
That is the threshold for provisionally discardable lines. */
while ((tem = tem >> 2) > 0)
many *= 2;
for (int i = 0; i < end; i++)
{
int nmatch;
if (equivs[i] == 0)
continue;
nmatch = counts[equivs[i]];
if (nmatch == 0)
discards[i] = 1;
else if (nmatch > many)
discards[i] = 2;
}
return discards;
}
/** Don't really discard the provisional lines except when they occur
in a run of discardables, with nonprovisionals at the beginning
and end. */
private void filterDiscards(final byte[] discards) {
final int end = buffered_lines;
for (int i = 0; i < end; i++)
{
/* Cancel provisional discards not in middle of run of discards. */
if (discards[i] == 2)
discards[i] = 0;
else if (discards[i] != 0)
{
/* We have found a nonprovisional discard. */
int j;
int length;
int provisional = 0;
/* Find end of this run of discardable lines.
Count how many are provisionally discardable. */
for (j = i; j < end; j++)
{
if (discards[j] == 0)
break;
if (discards[j] == 2)
++provisional;
}
/* Cancel provisional discards at end, and shrink the run. */
while (j > i && discards[j - 1] == 2) {
discards[--j] = 0; --provisional;
}
/* Now we have the length of a run of discardable lines
whose first and last are not provisional. */
length = j - i;
/* If 1/4 of the lines in the run are provisional,
cancel discarding of all provisional lines in the run. */
if (provisional * 4 > length)
{
while (j > i)
if (discards[--j] == 2)
discards[j] = 0;
}
else
{
int consec;
int minimum = 1;
int tem = length / 4;
/* MINIMUM is approximate square root of LENGTH/4.
A subrun of two or more provisionals can stand
when LENGTH is at least 16.
A subrun of 4 or more can stand when LENGTH >= 64. */
while ((tem = tem >> 2) > 0)
minimum *= 2;
minimum++;
/* Cancel any subrun of MINIMUM or more provisionals
within the larger run. */
for (j = 0, consec = 0; j < length; j++)
if (discards[i + j] != 2)
consec = 0;
else if (minimum == ++consec)
/* Back up to start of subrun, to cancel it all. */
j -= consec;
else if (minimum < consec)
discards[i + j] = 0;
/* Scan from beginning of run
until we find 3 or more nonprovisionals in a row
or until the first nonprovisional at least 8 lines in.
Until that point, cancel any provisionals. */
for (j = 0, consec = 0; j < length; j++)
{
if (j >= 8 && discards[i + j] == 1)
break;
if (discards[i + j] == 2) {
consec = 0; discards[i + j] = 0;
}
else if (discards[i + j] == 0)
consec = 0;
else
consec++;
if (consec == 3)
break;
}
/* I advances to the last line of the run. */
i += length - 1;
/* Same thing, from end. */
for (j = 0, consec = 0; j < length; j++)
{
if (j >= 8 && discards[i - j] == 1)
break;
if (discards[i - j] == 2) {
consec = 0; discards[i - j] = 0;
}
else if (discards[i - j] == 0)
consec = 0;
else
consec++;
if (consec == 3)
break;
}
}
}
}
}
/** Actually discard the lines.
@param discards flags lines to be discarded
*/
private void discard(final byte[] discards) {
final int end = buffered_lines;
int j = 0;
for (int i = 0; i < end; ++i)
if (no_discards || discards[i] == 0)
{
undiscarded[j] = equivs[i];
realindexes[j++] = i;
}
else
changed_flag[1+i] = true;
nondiscarded_lines = j;
}
file_data(Object[] data,Hashtable h) {
buffered_lines = data.length;
equivs = new int[buffered_lines];
undiscarded = new int[buffered_lines];
realindexes = new int[buffered_lines];
for (int i = 0; i < data.length; ++i) {
Integer ir = (Integer)h.get(data[i]);
if (ir == null)
h.put(data[i],new Integer(equivs[i] = equiv_max++));
else
equivs[i] = ir.intValue();
}
}
/** Adjust inserts/deletes of blank lines to join changes
as much as possible.
We do something when a run of changed lines include a blank
line at one end and have an excluded blank line at the other.
We are free to choose which blank line is included.
`compareseq' always chooses the one at the beginning,
but usually it is cleaner to consider the following blank line
to be the "change". The only exception is if the preceding blank line
would join this change to other changes.
@param f the file being compared against
*/
void shift_boundaries(file_data f) {
final boolean[] changed = changed_flag;
final boolean[] other_changed = f.changed_flag;
int i = 0;
int j = 0;
int i_end = buffered_lines;
int preceding = -1;
int other_preceding = -1;
for (;;)
{
int start, end, other_start;
/* Scan forwards to find beginning of another run of changes.
Also keep track of the corresponding point in the other file. */
while (i < i_end && !changed[1+i])
{
while (other_changed[1+j++])
/* Non-corresponding lines in the other file
will count as the preceding batch of changes. */
other_preceding = j;
i++;
}
if (i == i_end)
break;
start = i;
other_start = j;
for (;;)
{
/* Now find the end of this run of changes. */
while (i < i_end && changed[1+i]) i++;
end = i;
/* If the first changed line matches the following unchanged one,
and this run does not follow right after a previous run,
and there are no lines deleted from the other file here,
then classify the first changed line as unchanged
and the following line as changed in its place. */
/* You might ask, how could this run follow right after another?
Only because the previous run was shifted here. */
if (end != i_end
&& equivs[start] == equivs[end]
&& !other_changed[1+j]
&& end != i_end
&& !((preceding >= 0 && start == preceding)
|| (other_preceding >= 0
&& other_start == other_preceding)))
{
changed[1+end++] = true;
changed[1+start++] = false;
++i;
/* Since one line-that-matches is now before this run
instead of after, we must advance in the other file
to keep in synch. */
++j;
}
else
break;
}
preceding = i;
other_preceding = j;
}
}
/** Number of elements (lines) in this file. */
final int buffered_lines;
/** Vector, indexed by line number, containing an equivalence code for
each line. It is this vector that is actually compared with that
of another file to generate differences. */
private final int[] equivs;
/** Vector, like the previous one except that
the elements for discarded lines have been squeezed out. */
final int[] undiscarded;
/** Vector mapping virtual line numbers (not counting discarded lines)
to real ones (counting those lines). Both are origin-0. */
final int[] realindexes;
/** Total number of nondiscarded lines. */
int nondiscarded_lines;
/** Array, indexed by real origin-1 line number,
containing true for a line that is an insertion or a deletion.
The results of comparison are stored here. */
boolean[] changed_flag;
}
}
package jdiff;
import java.io.*;
import java.util.*;
/**
* Class to represent a single documentation difference.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class DiffOutput implements Comparable {
/** The package name for this difference. */
public String pkgName_ = null;
/** The class name for this difference, may be null. */
public String className_ = null;
/** The HTML named anchor identifier for this difference. */
public String id_ = null;
/** The title for this difference. */
public String title_ = null;
/** The text for this difference, with deleted and added words marked. */
public String text_ = null;
/** Constructor. */
public DiffOutput(String pkgName, String className, String id,
String title, String text) {
pkgName_ = pkgName;
className_ = className;
id_ = id;
title_ = title;
text_ = text;
}
/**
* Compare two DiffOutput objects, so they will appear in the correct
* package.
*/
public int compareTo(Object o) {
DiffOutput oDiffOutput = (DiffOutput)o;
int comp = pkgName_.compareTo(oDiffOutput.pkgName_);
if (comp != 0)
return comp;
// Always put the package-level output at the top - not yet working
// if (id_.compareTo("package") == 0)
// return -1;
return id_.compareTo(oDiffOutput.id_);
}
}