• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package jdiff;
2 
3 import java.util.*;
4 
5 /**
6  * Convert some remove and add operations into change operations.
7  *
8  * Once the numbers of members removed and added are known
9  * we can deduce more information about changes. For instance, if there are
10  * two methods with the same name, and one or more of them has a
11  * parameter type change, then this can only be reported as removing
12  * the old version(s) and adding the new version(s), because there are
13  * multiple methods with the same name.
14  *
15  * However, if only <i>one</i> method with a given name is removed, and
16  * only <i>one</i> method with the same name is added, we can convert these
17  * operations to a change operation. For constructors, this is true if
18  * the types are the same. For fields, the field names have to be the same,
19  * though this should never occur, since field names are unique.
20  *
21  * Another merge which can be made is if two or more methods with the same name
22  * were marked as removed and added because of changes other than signature.
23  *
24  * See the file LICENSE.txt for copyright details.
25  * @author Matthew Doar, mdoar@pobox.com
26  */
27 class MergeChanges {
28 
29     /**
30      * Convert some remove and add operations into change operations.
31      *
32      * Note that if a single thread modifies a collection directly while it is
33      * iterating over the collection with a fail-fast iterator, the iterator
34      * will throw java.util.ConcurrentModificationException
35      */
mergeRemoveAdd(APIDiff apiDiff)36     public static void mergeRemoveAdd(APIDiff apiDiff) {
37         // Go through all the ClassDiff objects searching for the above cases.
38         Iterator iter = apiDiff.packagesChanged.iterator();
39         while (iter.hasNext()) {
40             PackageDiff pkgDiff = (PackageDiff)(iter.next());
41             Iterator iter2 = pkgDiff.classesChanged.iterator();
42             while (iter2.hasNext()) {
43                 ClassDiff classDiff = (ClassDiff)(iter2.next());
44                 // Note: using iterators to step through the members gives a
45                 // ConcurrentModificationException exception with large files.
46                 // Constructors
47                 ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
48                 ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
49                 for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
50                     ConstructorAPI removedCtor = ctorArr[ctorIdx];
51                     mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
52                 }
53                 // Methods
54                 MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
55                 methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
56                 for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
57                     MethodAPI removedMethod = methodArr[methodIdx];
58                     // Only merge locally defined methods
59                     if (removedMethod.inheritedFrom_ == null)
60                         mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
61                 }
62                 // Fields
63                 FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
64                 fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
65                 for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
66                     FieldAPI removedField = fieldArr[fieldIdx];
67                     // Only merge locally defined fields
68                     if (removedField.inheritedFrom_ == null)
69                         mergeRemoveAddField(removedField, classDiff, pkgDiff);
70                 }
71             }
72         }
73     }
74 
75     /**
76      * Convert some removed and added constructors into changed constructors.
77      */
mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff)78     public static void mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff) {
79         // Search on the type of the constructor
80         int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
81         int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
82         int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
83         int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
84         if (startRemoved != -1 && startRemoved == endRemoved &&
85             startAdded != -1 && startAdded == endAdded) {
86             // There is only one constructor with the type of the
87             // removedCtor in both the removed and added constructors.
88             ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
89             // Create a MemberDiff for this change
90             MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
91             ctorDiff.oldType_ = removedCtor.getSignature();
92             ctorDiff.newType_ = addedCtor.getSignature(); // Should be the same as removedCtor.type
93             ctorDiff.oldExceptions_ = removedCtor.exceptions_;
94             ctorDiff.newExceptions_ = addedCtor.exceptions_;
95             ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
96             // Track changes in documentation
97             if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
98                 String type = ctorDiff.newType_;
99                 if (type.compareTo("void") == 0)
100                     type = "";
101                 String fqName = pkgDiff.name_ + "." + classDiff.name_;
102                 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
103                 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
104                 String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
105                 String title = link1 + "Class <b>" + classDiff.name_ +
106                     "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
107                 ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
108             }
109             classDiff.ctorsChanged.add(ctorDiff);
110             // Now remove the entries from the remove and add lists
111             classDiff.ctorsRemoved.remove(startRemoved);
112             classDiff.ctorsAdded.remove(startAdded);
113             if (trace && ctorDiff.modifiersChange_ != null)
114                 System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
115         }
116     }
117 
118     /**
119      * Convert some removed and added methods into changed methods.
120      */
mergeRemoveAddMethod(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)121     public static void mergeRemoveAddMethod(MethodAPI removedMethod,
122                                             ClassDiff classDiff,
123                                             PackageDiff pkgDiff) {
124         mergeSingleMethods(removedMethod, classDiff, pkgDiff);
125         mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
126     }
127 
128     /**
129      * Convert single removed and added methods into a changed method.
130      */
mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)131     public static void mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
132         // Search on the name of the method
133         int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
134         int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
135         int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
136         int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
137         if (startRemoved != -1 && startRemoved == endRemoved &&
138             startAdded != -1 && startAdded == endAdded) {
139             // There is only one method with the name of the
140             // removedMethod in both the removed and added methods.
141             MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
142             if (addedMethod.inheritedFrom_ == null) {
143                 // Create a MemberDiff for this change
144                 MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
145                 methodDiff.oldType_ = removedMethod.returnType_;
146                 methodDiff.newType_ = addedMethod.returnType_;
147                 methodDiff.oldSignature_ = removedMethod.getSignature();
148                 methodDiff.newSignature_ = addedMethod.getSignature();
149                 methodDiff.oldExceptions_ = removedMethod.exceptions_;
150                 methodDiff.newExceptions_ = addedMethod.exceptions_;
151                 // The addModifiersChange field may not have been
152                 // initialized yet if there were multiple methods of the same
153                 // name.
154                 diffMethods(methodDiff, removedMethod, addedMethod);
155                 methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
156                 // Track changes in documentation
157                 if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
158                     String sig = methodDiff.newSignature_;
159                     if (sig.compareTo("void") == 0)
160                         sig = "";
161                     String fqName = pkgDiff.name_ + "." + classDiff.name_;
162                     String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
163                     String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
164                     String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
165                     String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
166                         link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
167                     methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
168                 }
169                 classDiff.methodsChanged.add(methodDiff);
170                 // Now remove the entries from the remove and add lists
171                 classDiff.methodsRemoved.remove(startRemoved);
172                 classDiff.methodsAdded.remove(startAdded);
173                 if (trace) {
174                     System.out.println("Merged the removal and addition of method " +
175                                        removedMethod.name_ +
176                                        " into one change");
177                 }
178             } //if (addedMethod.inheritedFrom_ == null)
179         }
180     }
181 
182     /**
183      * Convert multiple removed and added methods into changed methods.
184      * This handles the case where the methods' signatures are unchanged, but
185      * something else changed.
186      */
mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff)187     public static void mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
188         // Search on the name and signature of the method
189         int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
190         int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
191         int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
192         int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
193         if (startRemoved != -1 && endRemoved != -1 &&
194             startAdded != -1 && endAdded != -1) {
195             // Find the index of the current removed method
196             int removedIdx = -1;
197             for (int i = startRemoved; i <= endRemoved; i++) {
198                 if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
199                     removedIdx = i;
200                     break;
201                 }
202             }
203             if (removedIdx == -1) {
204                 System.out.println("Error: removed method index not found");
205                 System.exit(5);
206             }
207             // Find the index of the added method with the same signature, if
208             // it exists, and make sure it is defined locally.
209             int addedIdx = -1;
210             for (int i = startAdded; i <= endAdded; i++) {
211                 MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
212                 if (addedMethod2.inheritedFrom_ == null &&
213                     removedMethod.equalSignatures(addedMethod2)) {
214                     addedIdx = i;
215                     break;
216                 }
217             }
218             if (addedIdx == -1)
219                 return;
220             MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
221             // Create a MemberDiff for this change
222             MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
223             methodDiff.oldType_ = removedMethod.returnType_;
224             methodDiff.newType_ = addedMethod.returnType_;
225             methodDiff.oldSignature_ = removedMethod.getSignature();
226             methodDiff.newSignature_ = addedMethod.getSignature();
227             methodDiff.oldExceptions_ = removedMethod.exceptions_;
228             methodDiff.newExceptions_ = addedMethod.exceptions_;
229                 // The addModifiersChange field may not have been
230                 // initialized yet if there were multiple methods of the same
231                 // name.
232                 diffMethods(methodDiff, removedMethod, addedMethod);
233             methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
234             // Track changes in documentation
235             if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
236                 String sig = methodDiff.newSignature_;
237                 if (sig.compareTo("void") == 0)
238                     sig = "";
239                 String fqName = pkgDiff.name_ + "." + classDiff.name_;
240                 String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
241                 String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
242                 String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
243                 String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
244                     link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
245                 methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
246             }
247             classDiff.methodsChanged.add(methodDiff);
248             // Now remove the entries from the remove and add lists
249             classDiff.methodsRemoved.remove(removedIdx);
250             classDiff.methodsAdded.remove(addedIdx);
251             if (trace) {
252                 System.out.println("Merged the removal and addition of method " +
253                                    removedMethod.name_ +
254                                    " into one change. There were multiple methods of this name.");
255             }
256         }
257     }
258 
259     /**
260      * Track changes in methods related to abstract, native, and
261      * synchronized modifiers here.
262      */
diffMethods(MemberDiff methodDiff, MethodAPI oldMethod, MethodAPI newMethod)263     public static void diffMethods(MemberDiff methodDiff,
264                                    MethodAPI oldMethod,
265                                    MethodAPI newMethod) {
266         // Abstract or not
267         if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
268             String changeText = "";
269             if (oldMethod.isAbstract_)
270                 changeText += "Changed from abstract to non-abstract.";
271             else
272                 changeText += "Changed from non-abstract to abstract.";
273             methodDiff.addModifiersChange(changeText);
274         }
275         // Native or not
276         if (Diff.showAllChanges &&
277         oldMethod.isNative_ != newMethod.isNative_) {
278             String changeText = "";
279             if (oldMethod.isNative_)
280                 changeText += "Changed from native to non-native.";
281             else
282                 changeText += "Changed from non-native to native.";
283             methodDiff.addModifiersChange(changeText);
284         }
285         // Synchronized or not
286         if (Diff.showAllChanges &&
287         oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
288             String changeText = "";
289             if (oldMethod.isSynchronized_)
290                 changeText += "Changed from synchronized to non-synchronized.";
291             else
292                 changeText += "Changed from non-synchronized to synchronized.";
293             methodDiff.addModifiersChange(changeText);
294         }
295     }
296 
297     /**
298      * Convert some removed and added fields into changed fields.
299      */
mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff)300     public static void mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff) {
301         // Search on the name of the field
302         int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
303         int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
304         int startAdded = classDiff.fieldsAdded.indexOf(removedField);
305         int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
306         if (startRemoved != -1 && startRemoved == endRemoved &&
307             startAdded != -1 && startAdded == endAdded) {
308             // There is only one field with the name of the
309             // removedField in both the removed and added fields.
310             FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
311             if (addedField.inheritedFrom_ == null) {
312                 // Create a MemberDiff for this change
313                 MemberDiff fieldDiff = new MemberDiff(removedField.name_);
314                 fieldDiff.oldType_ = removedField.type_;
315                 fieldDiff.newType_ = addedField.type_;
316                 fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
317                 // Track changes in documentation
318                 if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
319                     String fqName = pkgDiff.name_ + "." + classDiff.name_;
320                     String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
321                     String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
322                     String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
323                     String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
324                         link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
325                     fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
326                 }
327                 classDiff.fieldsChanged.add(fieldDiff);
328                 // Now remove the entries from the remove and add lists
329                 classDiff.fieldsRemoved.remove(startRemoved);
330                 classDiff.fieldsAdded.remove(startAdded);
331                 if (trace) {
332                     System.out.println("Merged the removal and addition of field " +
333                                        removedField.name_ +
334                                        " into one change");
335                 }
336             } //if (addedField.inheritedFrom == null)
337         }
338     }
339 
340     /** Set to enable increased logging verbosity for debugging. */
341     private static boolean trace = false;
342 
343 }