1package jdiff;
2
3import 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 */
27class 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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}
344