package jdiff;
|
|
import java.util.*;
|
|
/**
|
* Convert some remove and add operations into change operations.
|
*
|
* Once the numbers of members removed and added are known
|
* we can deduce more information about changes. For instance, if there are
|
* two methods with the same name, and one or more of them has a
|
* parameter type change, then this can only be reported as removing
|
* the old version(s) and adding the new version(s), because there are
|
* multiple methods with the same name.
|
*
|
* However, if only <i>one</i> method with a given name is removed, and
|
* only <i>one</i> method with the same name is added, we can convert these
|
* operations to a change operation. For constructors, this is true if
|
* the types are the same. For fields, the field names have to be the same,
|
* though this should never occur, since field names are unique.
|
*
|
* Another merge which can be made is if two or more methods with the same name
|
* were marked as removed and added because of changes other than signature.
|
*
|
* See the file LICENSE.txt for copyright details.
|
* @author Matthew Doar, mdoar@pobox.com
|
*/
|
class MergeChanges {
|
|
/**
|
* Convert some remove and add operations into change operations.
|
*
|
* Note that if a single thread modifies a collection directly while it is
|
* iterating over the collection with a fail-fast iterator, the iterator
|
* will throw java.util.ConcurrentModificationException
|
*/
|
public static void mergeRemoveAdd(APIDiff apiDiff) {
|
// Go through all the ClassDiff objects searching for the above cases.
|
Iterator iter = apiDiff.packagesChanged.iterator();
|
while (iter.hasNext()) {
|
PackageDiff pkgDiff = (PackageDiff)(iter.next());
|
Iterator iter2 = pkgDiff.classesChanged.iterator();
|
while (iter2.hasNext()) {
|
ClassDiff classDiff = (ClassDiff)(iter2.next());
|
// Note: using iterators to step through the members gives a
|
// ConcurrentModificationException exception with large files.
|
// Constructors
|
ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
|
ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
|
for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
|
ConstructorAPI removedCtor = ctorArr[ctorIdx];
|
mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
|
}
|
// Methods
|
MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
|
methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
|
for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
|
MethodAPI removedMethod = methodArr[methodIdx];
|
// Only merge locally defined methods
|
if (removedMethod.inheritedFrom_ == null)
|
mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
|
}
|
// Fields
|
FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
|
fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
|
for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
|
FieldAPI removedField = fieldArr[fieldIdx];
|
// Only merge locally defined fields
|
if (removedField.inheritedFrom_ == null)
|
mergeRemoveAddField(removedField, classDiff, pkgDiff);
|
}
|
}
|
}
|
}
|
|
/**
|
* Convert some removed and added constructors into changed constructors.
|
*/
|
public static void mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff) {
|
// Search on the type of the constructor
|
int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
|
int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
|
int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
|
int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
|
if (startRemoved != -1 && startRemoved == endRemoved &&
|
startAdded != -1 && startAdded == endAdded) {
|
// There is only one constructor with the type of the
|
// removedCtor in both the removed and added constructors.
|
ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
|
// Create a MemberDiff for this change
|
MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
|
ctorDiff.oldType_ = removedCtor.getSignature();
|
ctorDiff.newType_ = addedCtor.getSignature(); // Should be the same as removedCtor.type
|
ctorDiff.oldExceptions_ = removedCtor.exceptions_;
|
ctorDiff.newExceptions_ = addedCtor.exceptions_;
|
ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
|
// Track changes in documentation
|
if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
|
String type = ctorDiff.newType_;
|
if (type.compareTo("void") == 0)
|
type = "";
|
String fqName = pkgDiff.name_ + "." + classDiff.name_;
|
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
|
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
|
String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
|
String title = link1 + "Class <b>" + classDiff.name_ +
|
"</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
|
ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
|
}
|
classDiff.ctorsChanged.add(ctorDiff);
|
// Now remove the entries from the remove and add lists
|
classDiff.ctorsRemoved.remove(startRemoved);
|
classDiff.ctorsAdded.remove(startAdded);
|
if (trace && ctorDiff.modifiersChange_ != null)
|
System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
|
}
|
}
|
|
/**
|
* Convert some removed and added methods into changed methods.
|
*/
|
public static void mergeRemoveAddMethod(MethodAPI removedMethod,
|
ClassDiff classDiff,
|
PackageDiff pkgDiff) {
|
mergeSingleMethods(removedMethod, classDiff, pkgDiff);
|
mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
|
}
|
|
/**
|
* Convert single removed and added methods into a changed method.
|
*/
|
public static void mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
|
// Search on the name of the method
|
int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
|
int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
|
int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
|
int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
|
if (startRemoved != -1 && startRemoved == endRemoved &&
|
startAdded != -1 && startAdded == endAdded) {
|
// There is only one method with the name of the
|
// removedMethod in both the removed and added methods.
|
MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
|
if (addedMethod.inheritedFrom_ == null) {
|
// Create a MemberDiff for this change
|
MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
|
methodDiff.oldType_ = removedMethod.returnType_;
|
methodDiff.newType_ = addedMethod.returnType_;
|
methodDiff.oldSignature_ = removedMethod.getSignature();
|
methodDiff.newSignature_ = addedMethod.getSignature();
|
methodDiff.oldExceptions_ = removedMethod.exceptions_;
|
methodDiff.newExceptions_ = addedMethod.exceptions_;
|
// The addModifiersChange field may not have been
|
// initialized yet if there were multiple methods of the same
|
// name.
|
diffMethods(methodDiff, removedMethod, addedMethod);
|
methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
|
// Track changes in documentation
|
if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
|
String sig = methodDiff.newSignature_;
|
if (sig.compareTo("void") == 0)
|
sig = "";
|
String fqName = pkgDiff.name_ + "." + classDiff.name_;
|
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
|
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
|
String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
|
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
|
link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
|
methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
|
}
|
classDiff.methodsChanged.add(methodDiff);
|
// Now remove the entries from the remove and add lists
|
classDiff.methodsRemoved.remove(startRemoved);
|
classDiff.methodsAdded.remove(startAdded);
|
if (trace) {
|
System.out.println("Merged the removal and addition of method " +
|
removedMethod.name_ +
|
" into one change");
|
}
|
} //if (addedMethod.inheritedFrom_ == null)
|
}
|
}
|
|
/**
|
* Convert multiple removed and added methods into changed methods.
|
* This handles the case where the methods' signatures are unchanged, but
|
* something else changed.
|
*/
|
public static void mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
|
// Search on the name and signature of the method
|
int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
|
int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
|
int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
|
int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
|
if (startRemoved != -1 && endRemoved != -1 &&
|
startAdded != -1 && endAdded != -1) {
|
// Find the index of the current removed method
|
int removedIdx = -1;
|
for (int i = startRemoved; i <= endRemoved; i++) {
|
if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
|
removedIdx = i;
|
break;
|
}
|
}
|
if (removedIdx == -1) {
|
System.out.println("Error: removed method index not found");
|
System.exit(5);
|
}
|
// Find the index of the added method with the same signature, if
|
// it exists, and make sure it is defined locally.
|
int addedIdx = -1;
|
for (int i = startAdded; i <= endAdded; i++) {
|
MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
|
if (addedMethod2.inheritedFrom_ == null &&
|
removedMethod.equalSignatures(addedMethod2)) {
|
addedIdx = i;
|
break;
|
}
|
}
|
if (addedIdx == -1)
|
return;
|
MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
|
// Create a MemberDiff for this change
|
MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
|
methodDiff.oldType_ = removedMethod.returnType_;
|
methodDiff.newType_ = addedMethod.returnType_;
|
methodDiff.oldSignature_ = removedMethod.getSignature();
|
methodDiff.newSignature_ = addedMethod.getSignature();
|
methodDiff.oldExceptions_ = removedMethod.exceptions_;
|
methodDiff.newExceptions_ = addedMethod.exceptions_;
|
// The addModifiersChange field may not have been
|
// initialized yet if there were multiple methods of the same
|
// name.
|
diffMethods(methodDiff, removedMethod, addedMethod);
|
methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
|
// Track changes in documentation
|
if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
|
String sig = methodDiff.newSignature_;
|
if (sig.compareTo("void") == 0)
|
sig = "";
|
String fqName = pkgDiff.name_ + "." + classDiff.name_;
|
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
|
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
|
String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
|
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
|
link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
|
methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
|
}
|
classDiff.methodsChanged.add(methodDiff);
|
// Now remove the entries from the remove and add lists
|
classDiff.methodsRemoved.remove(removedIdx);
|
classDiff.methodsAdded.remove(addedIdx);
|
if (trace) {
|
System.out.println("Merged the removal and addition of method " +
|
removedMethod.name_ +
|
" into one change. There were multiple methods of this name.");
|
}
|
}
|
}
|
|
/**
|
* Track changes in methods related to abstract, native, and
|
* synchronized modifiers here.
|
*/
|
public static void diffMethods(MemberDiff methodDiff,
|
MethodAPI oldMethod,
|
MethodAPI newMethod) {
|
// Abstract or not
|
if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
|
String changeText = "";
|
if (oldMethod.isAbstract_)
|
changeText += "Changed from abstract to non-abstract.";
|
else
|
changeText += "Changed from non-abstract to abstract.";
|
methodDiff.addModifiersChange(changeText);
|
}
|
// Native or not
|
if (Diff.showAllChanges &&
|
oldMethod.isNative_ != newMethod.isNative_) {
|
String changeText = "";
|
if (oldMethod.isNative_)
|
changeText += "Changed from native to non-native.";
|
else
|
changeText += "Changed from non-native to native.";
|
methodDiff.addModifiersChange(changeText);
|
}
|
// Synchronized or not
|
if (Diff.showAllChanges &&
|
oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
|
String changeText = "";
|
if (oldMethod.isSynchronized_)
|
changeText += "Changed from synchronized to non-synchronized.";
|
else
|
changeText += "Changed from non-synchronized to synchronized.";
|
methodDiff.addModifiersChange(changeText);
|
}
|
}
|
|
/**
|
* Convert some removed and added fields into changed fields.
|
*/
|
public static void mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff) {
|
// Search on the name of the field
|
int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
|
int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
|
int startAdded = classDiff.fieldsAdded.indexOf(removedField);
|
int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
|
if (startRemoved != -1 && startRemoved == endRemoved &&
|
startAdded != -1 && startAdded == endAdded) {
|
// There is only one field with the name of the
|
// removedField in both the removed and added fields.
|
FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
|
if (addedField.inheritedFrom_ == null) {
|
// Create a MemberDiff for this change
|
MemberDiff fieldDiff = new MemberDiff(removedField.name_);
|
fieldDiff.oldType_ = removedField.type_;
|
fieldDiff.newType_ = addedField.type_;
|
fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
|
// Track changes in documentation
|
if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
|
String fqName = pkgDiff.name_ + "." + classDiff.name_;
|
String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
|
String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
|
String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
|
String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
|
link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
|
fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
|
}
|
classDiff.fieldsChanged.add(fieldDiff);
|
// Now remove the entries from the remove and add lists
|
classDiff.fieldsRemoved.remove(startRemoved);
|
classDiff.fieldsAdded.remove(startAdded);
|
if (trace) {
|
System.out.println("Merged the removal and addition of field " +
|
removedField.name_ +
|
" into one change");
|
}
|
} //if (addedField.inheritedFrom == null)
|
}
|
}
|
|
/** Set to enable increased logging verbosity for debugging. */
|
private static boolean trace = false;
|
|
}
|