• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package jdiff;
2 
3 import java.util.*;
4 
5 /**
6  * This class contains method to compare two API objects.
7  * The differences are stored in an APIDiff object.
8  *
9  * See the file LICENSE.txt for copyright details.
10  * @author Matthew Doar, mdoar@pobox.com
11  */
12 public class APIComparator {
13 
14     /**
15      * Top-level object representing the differences between two APIs.
16      * It is this object which is used to generate the report later on.
17      */
18     public APIDiff apiDiff;
19 
20     /**
21      * Package-level object representing the differences between two packages.
22      * This object is also used to determine which file to write documentation
23      * differences into.
24      */
25     public PackageDiff pkgDiff;
26 
27     /** Default constructor. */
APIComparator()28     public APIComparator() {
29         apiDiff = new APIDiff();
30     }
31 
32     /** For easy local access to the old API object. */
33     private static API oldAPI_;
34     /** For easy local access to the new API object. */
35     private static API newAPI_;
36 
37     /**
38      * Compare two APIs.
39      */
compareAPIs(API oldAPI, API newAPI)40     public void compareAPIs(API oldAPI, API newAPI) {
41         System.out.println("JDiff: comparing the old and new APIs ...");
42         oldAPI_ = oldAPI;
43         newAPI_ = newAPI;
44 
45         double differs = 0.0;
46 
47         apiDiff.oldAPIName_ = oldAPI.name_;
48         apiDiff.newAPIName_ = newAPI.name_;
49 
50         Collections.sort(oldAPI.packages_);
51         Collections.sort(newAPI.packages_);
52 
53         // Find packages which were removed in the new API
54         Iterator iter = oldAPI.packages_.iterator();
55         while (iter.hasNext()) {
56             PackageAPI oldPkg = (PackageAPI)(iter.next());
57             // This search is looking for an *exact* match. This is true in
58             // all the *API classes.
59             int idx = Collections.binarySearch(newAPI.packages_, oldPkg);
60             if (idx < 0) {
61                 // If there an instance of a package with the same name
62                 // in both the old and new API, then treat it as changed,
63                 // rather than removed and added. There will never be more than
64                 // one instance of a package with the same name in an API.
65                 int existsNew = newAPI.packages_.indexOf(oldPkg);
66                 if (existsNew != -1) {
67                     // Package by the same name exists in both APIs
68                     // but there has been some or other change.
69                     differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(existsNew)));
70                 }  else {
71                     if (trace)
72                         System.out.println("Package " + oldPkg.name_ + " was removed");
73                     apiDiff.packagesRemoved.add(oldPkg);
74                     differs += 1.0;
75                 }
76             } else {
77                 // The package exists unchanged in name or doc, but may
78                 // differ in classes and their members, so it still needs to
79                 // be compared.
80                 differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(idx)));
81             }
82         } // while (iter.hasNext())
83 
84         // Find packages which were added or changed in the new API
85         iter = newAPI.packages_.iterator();
86         while (iter.hasNext()) {
87             PackageAPI newPkg = (PackageAPI)(iter.next());
88             int idx = Collections.binarySearch(oldAPI.packages_, newPkg);
89             if (idx < 0) {
90                 // See comments above
91                 int existsOld = oldAPI.packages_.indexOf(newPkg);
92                 if (existsOld != -1) {
93                     // Don't mark a package as added or compare it
94                     // if it was already marked as changed
95                 } else {
96                     if (trace)
97                         System.out.println("Package " + newPkg.name_ + " was added");
98                     apiDiff.packagesAdded.add(newPkg);
99                     differs += 1.0;
100                 }
101             } else {
102                 // It will already have been compared above.
103             }
104         } // while (iter.hasNext())
105 
106         // Now that the numbers of members removed and added are known
107         // we can deduce more information about changes.
108         MergeChanges.mergeRemoveAdd(apiDiff);
109 
110 // The percent change statistic reported for all elements in each API is
111 // defined recursively as follows:
112 //
113 // %age change = 100 * (added + removed + 2*changed)
114 //               -----------------------------------
115 //               sum of public elements in BOTH APIs
116 //
117 // The definition ensures that if all classes are removed and all new classes
118 // added, the change will be 100%.
119 // Evaluation of the visibility of elements has already been done when the
120 // XML was written out.
121 // Note that this doesn't count changes in the modifiers of classes and
122 // packages. Other changes in members are counted.
123         Long denom = new Long(oldAPI.packages_.size() + newAPI.packages_.size());
124         // This should never be zero because an API always has packages?
125         if (denom.intValue() == 0) {
126             System.out.println("Error: no packages found in the APIs.");
127             return;
128         }
129         if (trace)
130             System.out.println("Top level changes: " + differs + "/" + denom.intValue());
131         differs = (100.0 * differs)/denom.doubleValue();
132 
133         // Some differences such as documentation changes are not tracked in
134         // the difference statistic, so a value of 0.0 does not mean that there
135         // were no differences between the APIs.
136         apiDiff.pdiff = differs;
137         Double percentage = new Double(differs);
138         int approxPercentage = percentage.intValue();
139         if (approxPercentage == 0)
140             System.out.println(" Approximately " + percentage + "% difference between the APIs");
141         else
142             System.out.println(" Approximately " + approxPercentage + "% difference between the APIs");
143 
144         Diff.closeDiffFile();
145     }
146 
147     /**
148      * Compare two packages.
149      */
comparePackages(PackageAPI oldPkg, PackageAPI newPkg)150     public double comparePackages(PackageAPI oldPkg, PackageAPI newPkg) {
151         if (trace)
152             System.out.println("Comparing old package " + oldPkg.name_ +
153                                " and new package " + newPkg.name_);
154         pkgDiff = new PackageDiff(oldPkg.name_);
155         double differs = 0.0;
156 
157         Collections.sort(oldPkg.classes_);
158         Collections.sort(newPkg.classes_);
159 
160         // Find classes which were removed in the new package
161         Iterator iter = oldPkg.classes_.iterator();
162         while (iter.hasNext()) {
163             ClassAPI oldClass = (ClassAPI)(iter.next());
164             // This search is looking for an *exact* match. This is true in
165             // all the *API classes.
166             int idx = Collections.binarySearch(newPkg.classes_, oldClass);
167             if (idx < 0) {
168                 // If there an instance of a class with the same name
169                 // in both the old and new package, then treat it as changed,
170                 // rather than removed and added. There will never be more than
171                 // one instance of a class with the same name in a package.
172                 int existsNew = newPkg.classes_.indexOf(oldClass);
173                 if (existsNew != -1) {
174                     // Class by the same name exists in both packages
175                     // but there has been some or other change.
176                     differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(existsNew)), pkgDiff);
177                 }  else {
178                     if (trace)
179                         System.out.println("  Class " + oldClass.name_ + " was removed");
180                     pkgDiff.classesRemoved.add(oldClass);
181                     differs += 1.0;
182                 }
183             } else {
184                 // The class exists unchanged in name or modifiers, but may
185                 // differ in members, so it still needs to be compared.
186                 differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(idx)), pkgDiff);
187             }
188         } // while (iter.hasNext())
189 
190         // Find classes which were added or changed in the new package
191         iter = newPkg.classes_.iterator();
192         while (iter.hasNext()) {
193             ClassAPI newClass = (ClassAPI)(iter.next());
194             int idx = Collections.binarySearch(oldPkg.classes_, newClass);
195             if (idx < 0) {
196                 // See comments above
197                 int existsOld = oldPkg.classes_.indexOf(newClass);
198                 if (existsOld != -1) {
199                     // Don't mark a class as added or compare it
200                     // if it was already marked as changed
201                 } else {
202                     if (trace)
203                         System.out.println("  Class " + newClass.name_ + " was added");
204                     pkgDiff.classesAdded.add(newClass);
205                     differs += 1.0;
206                 }
207             } else {
208                 // It will already have been compared above.
209             }
210         } // while (iter.hasNext())
211 
212         // Check if the only change was in documentation. Bug 472521.
213         boolean differsFlag = false;
214         if (docChanged(oldPkg.doc_, newPkg.doc_)) {
215             String link = "<a href=\"pkg_" + oldPkg.name_ + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
216             String id = oldPkg.name_ + "!package";
217             String title = link + "Package <b>" + oldPkg.name_ + "</b></a>";
218             pkgDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, null, oldPkg.doc_, newPkg.doc_, id, title);
219             differsFlag = true;
220         }
221 
222         // Only add to the parent Diff object if some difference has been found
223         if (differs != 0.0 || differsFlag)
224             apiDiff.packagesChanged.add(pkgDiff);
225 
226         Long denom = new Long(oldPkg.classes_.size() + newPkg.classes_.size());
227         // This should never be zero because a package always has classes?
228         if (denom.intValue() == 0) {
229             System.out.println("Warning: no classes found in the package " + oldPkg.name_);
230             return 0.0;
231         }
232         if (trace)
233             System.out.println("Package " + pkgDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
234         pkgDiff.pdiff = 100.0 * differs/denom.doubleValue();
235         return differs/denom.doubleValue();
236     } // comparePackages()
237 
238     /**
239      * Compare two classes.
240      *
241      * Need to compare constructors, methods and fields.
242      */
compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff)243     public double compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff) {
244         if (trace)
245             System.out.println("  Comparing old class " + oldClass.name_ +
246                                " and new class " + newClass.name_);
247         boolean differsFlag = false;
248         double differs = 0.0;
249         ClassDiff classDiff = new ClassDiff(oldClass.name_);
250         classDiff.isInterface_ = newClass.isInterface_; // Used in the report
251 
252         // Track changes in modifiers - class or interface
253         if (oldClass.isInterface_ != newClass.isInterface_) {
254             classDiff.modifiersChange_  = "Changed from ";
255             if (oldClass.isInterface_)
256                 classDiff.modifiersChange_ += "an interface to a class.";
257             else
258                 classDiff.modifiersChange_ += "a class to an interface.";
259             differsFlag = true;
260         }
261         // Track changes in inheritance
262         String inheritanceChange = ClassDiff.diff(oldClass, newClass);
263         if (inheritanceChange != null) {
264             classDiff.inheritanceChange_ = inheritanceChange;
265             differsFlag = true;
266         }
267         // Abstract or not
268         if (oldClass.isAbstract_ != newClass.isAbstract_) {
269             String changeText = "";
270             if (oldClass.isAbstract_)
271                 changeText += "Changed from abstract to non-abstract.";
272             else
273                 changeText += "Changed from non-abstract to abstract.";
274             classDiff.addModifiersChange(changeText);
275             differsFlag = true;
276         }
277         // Track changes in documentation
278         if (docChanged(oldClass.doc_, newClass.doc_)) {
279             String fqName = pkgDiff.name_ + "." + classDiff.name_;
280             String link = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
281             String id = pkgDiff.name_ + "." + classDiff.name_ + "!class";
282             String title = link + "Class <b>" + classDiff.name_ + "</b></a>";
283             classDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_,
284  classDiff.name_, oldClass.doc_, newClass.doc_, id, title);
285             differsFlag = true;
286         }
287         // All other modifiers
288         String modifiersChange = oldClass.modifiers_.diff(newClass.modifiers_);
289         if (modifiersChange != null) {
290             differsFlag = true;
291             if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
292                 System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + pkgDiff.name_ + "." + newClass.name_);
293 
294             }
295         }
296         classDiff.addModifiersChange(modifiersChange);
297 
298         // Track changes in members
299         boolean differsCtors =
300             compareAllCtors(oldClass, newClass, classDiff);
301         boolean differsMethods =
302             compareAllMethods(oldClass, newClass, classDiff);
303         boolean differsFields =
304             compareAllFields(oldClass, newClass, classDiff);
305         if (differsCtors || differsMethods || differsFields)
306             differsFlag = true;
307 
308         if (trace) {
309             System.out.println("  Ctors differ? " + differsCtors +
310                 ", Methods differ? " + differsMethods +
311                 ", Fields differ? " + differsFields);
312         }
313 
314         // Only add to the parent if some difference has been found
315         if (differsFlag)
316             pkgDiff.classesChanged.add(classDiff);
317 
318         // Get the numbers of affected elements from the classDiff object
319          differs =
320             classDiff.ctorsRemoved.size() + classDiff.ctorsAdded.size() +
321             classDiff.ctorsChanged.size() +
322             classDiff.methodsRemoved.size() + classDiff.methodsAdded.size() +
323             classDiff.methodsChanged.size() +
324             classDiff.fieldsRemoved.size() + classDiff.fieldsAdded.size() +
325             classDiff.fieldsChanged.size();
326          Long denom = new Long(
327              oldClass.ctors_.size() +
328              numLocalMethods(oldClass.methods_) +
329              numLocalFields(oldClass.fields_) +
330              newClass.ctors_.size() +
331              numLocalMethods(newClass.methods_) +
332              numLocalFields(newClass.fields_));
333          if (denom.intValue() == 0) {
334              // This is probably a placeholder interface, but documentation
335              // or modifiers etc may have changed
336              if (differsFlag) {
337                  classDiff.pdiff = 0.0; // 100.0 is too much
338                  return 1.0;
339              } else {
340                  return 0.0;
341              }
342          }
343          // Handle the case where the only change is in documentation or
344          // the modifiers
345          if (differsFlag && differs == 0.0) {
346              differs = 1.0;
347          }
348          if (trace)
349              System.out.println("  Class " + classDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
350          classDiff.pdiff = 100.0 * differs/denom.doubleValue();
351          return differs/denom.doubleValue();
352     } // compareClasses()
353 
354     /**
355      * Compare all the constructors in two classes.
356      *
357      * The compareTo method in the ConstructorAPI class acts only upon the type.
358      */
compareAllCtors(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)359     public boolean compareAllCtors(ClassAPI oldClass, ClassAPI newClass,
360                                    ClassDiff classDiff) {
361         if (trace)
362             System.out.println("    Comparing constructors: #old " +
363               oldClass.ctors_.size() + ", #new " + newClass.ctors_.size());
364         boolean differs = false;
365         boolean singleCtor = false; // Set if there is only one ctor
366 
367         Collections.sort(oldClass.ctors_);
368         Collections.sort(newClass.ctors_);
369 
370         // Find ctors which were removed in the new class
371         Iterator iter = oldClass.ctors_.iterator();
372         while (iter.hasNext()) {
373             ConstructorAPI oldCtor = (ConstructorAPI)(iter.next());
374             int idx = Collections.binarySearch(newClass.ctors_, oldCtor);
375             if (idx < 0) {
376                 int oldSize = oldClass.ctors_.size();
377                 int newSize = newClass.ctors_.size();
378                 if (oldSize == 1 && oldSize == newSize) {
379                     // If there is one constructor in the oldClass and one
380                     // constructor in the new class, then mark it as changed
381                     MemberDiff memberDiff = new MemberDiff(oldClass.name_);
382                     memberDiff.oldType_ = oldCtor.getSignature();
383                     memberDiff.oldExceptions_ = oldCtor.exceptions_;
384                     ConstructorAPI newCtor  = (ConstructorAPI)(newClass.ctors_.get(0));
385                     memberDiff.newType_ = newCtor.getSignature();
386                     memberDiff.newExceptions_ = newCtor.exceptions_;
387                     // Track changes in documentation
388                     if (docChanged(oldCtor.doc_, newCtor.doc_)) {
389                         String type = memberDiff.newType_;
390                         if (type.compareTo("void") == 0)
391                             type = "";
392                         String fqName = pkgDiff.name_ + "." + classDiff.name_;
393                         String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
394                         String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
395                         String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
396                         String title = link1 + "Class <b>" + classDiff.name_ +
397                             "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
398                         memberDiff.documentationChange_ = Diff.saveDocDiffs(
399                             pkgDiff.name_, classDiff.name_, oldCtor.doc_, newCtor.doc_, id, title);
400                     }
401                     String modifiersChange = oldCtor.modifiers_.diff(newCtor.modifiers_);
402                     if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
403                         System.out.println("JDiff: warning: change from deprecated to undeprecated for a constructor in class" + newClass.name_);
404                     }
405                     memberDiff.addModifiersChange(modifiersChange);
406                     if (trace)
407                         System.out.println("    The single constructor was changed");
408                     classDiff.ctorsChanged.add(memberDiff);
409                     singleCtor = true;
410                 } else {
411                     if (trace)
412                         System.out.println("    Constructor " + oldClass.name_ + " was removed");
413                     classDiff.ctorsRemoved.add(oldCtor);
414                 }
415                 differs = true;
416             }
417         } // while (iter.hasNext())
418 
419         // Find ctors which were added in the new class
420         iter = newClass.ctors_.iterator();
421         while (iter.hasNext()) {
422             ConstructorAPI newCtor = (ConstructorAPI)(iter.next());
423             int idx = Collections.binarySearch(oldClass.ctors_, newCtor);
424             if (idx < 0) {
425                 if (!singleCtor) {
426                     if (trace)
427                         System.out.println("    Constructor " + oldClass.name_ + " was added");
428                     classDiff.ctorsAdded.add(newCtor);
429                     differs = true;
430                 }
431             }
432         } // while (iter.hasNext())
433 
434         return differs;
435     } // compareAllCtors()
436 
437     /**
438      * Compare all the methods in two classes.
439      *
440      * We have to deal with the cases where:
441      *  - there is only one method with a given name, but its signature changes
442      *  - there is more than one method with the same name, and some of them
443      *    may have signature changes
444      * The simplest way to deal with this is to make the MethodAPI comparator
445      * check the params and return type, as well as the name. This means that
446      * changing a parameter's type would cause the method to be seen as
447      * removed and added. To avoid this for the simple case, check for before
448      * recording a method as removed or added.
449      */
compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)450     public boolean compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff) {
451         if (trace)
452             System.out.println("    Comparing methods: #old " +
453                                oldClass.methods_.size() + ", #new " +
454                                newClass.methods_.size());
455         boolean differs = false;
456 
457         Collections.sort(oldClass.methods_);
458         Collections.sort(newClass.methods_);
459 
460         // Find methods which were removed in the new class
461         Iterator iter = oldClass.methods_.iterator();
462         while (iter.hasNext()) {
463             MethodAPI oldMethod = (MethodAPI)(iter.next());
464             int idx = -1;
465             MethodAPI[] methodArr = new MethodAPI[newClass.methods_.size()];
466             methodArr = (MethodAPI[])newClass.methods_.toArray(methodArr);
467             for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
468                 MethodAPI newMethod = methodArr[methodIdx];
469                 if (oldMethod.compareTo(newMethod) == 0) {
470                     idx  = methodIdx;
471                     break;
472                 }
473             }
474 // NOTE: there was a problem with the binarySearch for
475 // java.lang.Byte.toString(byte b) returning -16 when the compareTo method
476 // returned 0 on entry 13. Changed to use arrays instead, so maybe it was
477 // an issue with methods having another List of params used indirectly by
478 // compareTo(), unlike constructors and fields?
479 //            int idx = Collections.binarySearch(newClass.methods_, oldMethod);
480             if (idx < 0) {
481                 // If there is only one instance of a method with this name
482                 // in both the old and new class, then treat it as changed,
483                 // rather than removed and added.
484                 // Find how many instances of this method name there are in
485                 // the old and new class. The equals comparator is just on
486                 // the method name.
487                 int startOld = oldClass.methods_.indexOf(oldMethod);
488                 int endOld = oldClass.methods_.lastIndexOf(oldMethod);
489                 int startNew = newClass.methods_.indexOf(oldMethod);
490                 int endNew = newClass.methods_.lastIndexOf(oldMethod);
491 
492                 if (startOld != -1 && startOld == endOld &&
493                     startNew != -1 && startNew == endNew) {
494                     MethodAPI newMethod = (MethodAPI)(newClass.methods_.get(startNew));
495                     // Only one method with that name exists in both packages,
496                     // so it is valid to compare the two methods. We know it
497                     // has changed, because the binarySearch did not find it.
498                     if (oldMethod.inheritedFrom_ == null ||
499                         newMethod.inheritedFrom_ == null) {
500                         // We also know that at least one of the methods is
501                         // locally defined.
502                         compareMethods(oldMethod, newMethod, classDiff);
503                         differs = true;
504                     }
505                 } else if (oldMethod.inheritedFrom_ == null) {
506                     // Only concerned with locally defined methods
507                     if (trace)
508                         System.out.println("    Method " + oldMethod.name_ +
509                                            "(" + oldMethod.getSignature() +
510                                            ") was removed");
511                     classDiff.methodsRemoved.add(oldMethod);
512                     differs = true;
513                 }
514             }
515         } // while (iter.hasNext())
516 
517         // Find methods which were added in the new class
518         iter = newClass.methods_.iterator();
519         while (iter.hasNext()) {
520             MethodAPI newMethod = (MethodAPI)(iter.next());
521             // Only concerned with locally defined methods
522             if (newMethod.inheritedFrom_ != null)
523                 continue;
524             int idx = -1;
525             MethodAPI[] methodArr = new MethodAPI[oldClass.methods_.size()];
526             methodArr = (MethodAPI[])oldClass.methods_.toArray(methodArr);
527             for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
528                 MethodAPI oldMethod = methodArr[methodIdx];
529                 if (newMethod.compareTo(oldMethod) == 0) {
530                     idx  = methodIdx;
531                     break;
532                 }
533             }
534 // See note above about searching an array instead of binarySearch
535 //            int idx = Collections.binarySearch(oldClass.methods_, newMethod);
536             if (idx < 0) {
537                 // See comments above
538                 int startOld = oldClass.methods_.indexOf(newMethod);
539                 int endOld = oldClass.methods_.lastIndexOf(newMethod);
540                 int startNew = newClass.methods_.indexOf(newMethod);
541                 int endNew = newClass.methods_.lastIndexOf(newMethod);
542 
543                 if (startOld != -1 && startOld == endOld &&
544                     startNew != -1 && startNew == endNew) {
545                     // Don't mark a method as added if it was marked as changed
546                     // The comparison will have been done just above here.
547                 } else {
548                     if (trace)
549                         System.out.println("    Method " + newMethod.name_ +
550                                            "(" + newMethod.getSignature() + ") was added");
551                     classDiff.methodsAdded.add(newMethod);
552                     differs = true;
553                 }
554             }
555         } // while (iter.hasNext())
556 
557         return differs;
558     } // compareAllMethods()
559 
560     /**
561      * Compare two methods which have the same name.
562      */
compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff)563     public boolean compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff) {
564         MemberDiff methodDiff = new MemberDiff(oldMethod.name_);
565         boolean differs = false;
566         // Check changes in return type
567         methodDiff.oldType_ = oldMethod.returnType_;
568         methodDiff.newType_ = newMethod.returnType_;
569         if (oldMethod.returnType_.compareTo(newMethod.returnType_) != 0) {
570             differs = true;
571         }
572         // Check changes in signature
573         String oldSig = oldMethod.getSignature();
574         String newSig = newMethod.getSignature();
575         methodDiff.oldSignature_ = oldSig;
576         methodDiff.newSignature_ = newSig;
577         if (oldSig.compareTo(newSig) != 0) {
578             differs = true;
579         }
580         // Changes in inheritance
581         int inh = changedInheritance(oldMethod.inheritedFrom_, newMethod.inheritedFrom_);
582         if (inh != 0)
583             differs = true;
584         if (inh == 1) {
585             methodDiff.addModifiersChange("Method was locally defined, but is now inherited from " + linkToClass(newMethod, true) + ".");
586             methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
587         } else if (inh == 2) {
588             methodDiff.addModifiersChange("Method was inherited from " + linkToClass(oldMethod, false) + ", but is now defined locally.");
589         } else if (inh == 3) {
590             methodDiff.addModifiersChange("Method was inherited from " +
591                                           linkToClass(oldMethod, false) + ", and is now inherited from " + linkToClass(newMethod, true) + ".");
592             methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
593         }
594         // Abstract or not
595         if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
596             String changeText = "";
597             if (oldMethod.isAbstract_)
598                 changeText += "Changed from abstract to non-abstract.";
599             else
600                 changeText += "Changed from non-abstract to abstract.";
601             methodDiff.addModifiersChange(changeText);
602             differs = true;
603         }
604         // Native or not
605         if (Diff.showAllChanges &&
606 	    oldMethod.isNative_ != newMethod.isNative_) {
607             String changeText = "";
608             if (oldMethod.isNative_)
609                 changeText += "Changed from native to non-native.";
610             else
611                 changeText += "Changed from non-native to native.";
612             methodDiff.addModifiersChange(changeText);
613             differs = true;
614         }
615         // Synchronized or not
616         if (Diff.showAllChanges &&
617 	    oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
618             String changeText = "";
619             if (oldMethod.isSynchronized_)
620                 changeText += "Changed from synchronized to non-synchronized.";
621             else
622                 changeText += "Changed from non-synchronized to synchronized.";
623             methodDiff.addModifiersChange(changeText);
624             differs = true;
625         }
626 
627         // Check changes in exceptions thrown
628         methodDiff.oldExceptions_ = oldMethod.exceptions_;
629         methodDiff.newExceptions_ = newMethod.exceptions_;
630         if (oldMethod.exceptions_.compareTo(newMethod.exceptions_) != 0) {
631             differs = true;
632         }
633 
634         // Track changes in documentation
635         if (docChanged(oldMethod.doc_, newMethod.doc_)) {
636             String sig = methodDiff.newSignature_;
637             if (sig.compareTo("void") == 0)
638                 sig = "";
639             String fqName = pkgDiff.name_ + "." + classDiff.name_;
640             String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
641             String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
642             String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
643             String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
644                 link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
645             methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldMethod.doc_, newMethod.doc_, id, title);
646             differs = true;
647         }
648 
649         // All other modifiers
650         String modifiersChange = oldMethod.modifiers_.diff(newMethod.modifiers_);
651         if (modifiersChange != null) {
652             differs = true;
653             if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
654                 System.out.println("JDiff: warning: change from deprecated to undeprecated for method " +  classDiff.name_ + "." + newMethod.name_);
655 
656             }
657         }
658         methodDiff.addModifiersChange(modifiersChange);
659 
660         // Only add to the parent if some difference has been found
661         if (differs) {
662             if (trace) {
663                 System.out.println("    Method " + newMethod.name_ +
664                     " was changed: old: " +
665                    oldMethod.returnType_ + "(" + oldSig + "), new: " +
666                    newMethod.returnType_ + "(" + newSig + ")");
667                 if (methodDiff.modifiersChange_ != null)
668                     System.out.println("    Modifier change: " + methodDiff.modifiersChange_);
669             }
670             classDiff.methodsChanged.add(methodDiff);
671         }
672 
673         return differs;
674     } // compareMethods()
675 
676     /**
677      * Compare all the fields in two classes.
678      */
compareAllFields(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff)679     public boolean compareAllFields(ClassAPI oldClass, ClassAPI newClass,
680                                     ClassDiff classDiff) {
681         if (trace)
682             System.out.println("    Comparing fields: #old " +
683                                oldClass.fields_.size() + ", #new "
684                                + newClass.fields_.size());
685         boolean differs = false;
686 
687         Collections.sort(oldClass.fields_);
688         Collections.sort(newClass.fields_);
689 
690         // Find fields which were removed in the new class
691         Iterator iter = oldClass.fields_.iterator();
692         while (iter.hasNext()) {
693             FieldAPI oldField = (FieldAPI)(iter.next());
694             int idx = Collections.binarySearch(newClass.fields_, oldField);
695             if (idx < 0) {
696                 // If there an instance of a field with the same name
697                 // in both the old and new class, then treat it as changed,
698                 // rather than removed and added. There will never be more than
699                 // one instance of a field with the same name in a class.
700                 int existsNew = newClass.fields_.indexOf(oldField);
701                 if (existsNew != -1) {
702                     FieldAPI newField = (FieldAPI)(newClass.fields_.get(existsNew));
703                     if (oldField.inheritedFrom_ == null ||
704                         newField.inheritedFrom_ == null) {
705                         // We also know that one of the fields is locally defined.
706                         MemberDiff memberDiff = new MemberDiff(oldField.name_);
707                         memberDiff.oldType_ = oldField.type_;
708                         memberDiff.newType_ = newField.type_;
709                         // Changes in inheritance
710                         int inh = changedInheritance(oldField.inheritedFrom_, newField.inheritedFrom_);
711                         if (inh != 0)
712                             differs = true;
713                         if (inh == 1) {
714                             memberDiff.addModifiersChange("Field was locally defined, but is now inherited from " + linkToClass(newField, true) + ".");
715                             memberDiff.inheritedFrom_ = newField.inheritedFrom_;
716                         } else if (inh == 2) {
717                             memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", but is now defined locally.");
718                         } else if (inh == 3) {
719                             memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", and is now inherited from " + linkToClass(newField, true) + ".");
720                             memberDiff.inheritedFrom_ = newField.inheritedFrom_;
721                         }
722                         // Transient or not
723                         if (oldField.isTransient_ != newField.isTransient_) {
724                             String changeText = "";
725                             if (oldField.isTransient_)
726                                 changeText += "Changed from transient to non-transient.";
727                             else
728                                 changeText += "Changed from non-transient to transient.";
729                             memberDiff.addModifiersChange(changeText);
730                             differs = true;
731                         }
732                         // Volatile or not
733                         if (oldField.isVolatile_ != newField.isVolatile_) {
734                             String changeText = "";
735                             if (oldField.isVolatile_)
736                                 changeText += "Changed from volatile to non-volatile.";
737                             else
738                                 changeText += "Changed from non-volatile to volatile.";
739                             memberDiff.addModifiersChange(changeText);
740                             differs = true;
741                         }
742                         // Change in value of the field
743                         if (oldField.value_ != null &&
744                             newField.value_ != null &&
745                             oldField.value_.compareTo(newField.value_) != 0) {
746                             String changeText = "Changed in value from " + oldField.value_
747                                 + " to " + newField.value_ +".";
748                             memberDiff.addModifiersChange(changeText);
749                             differs = true;
750                         }
751                         // Track changes in documentation
752                         if (docChanged(oldField.doc_, newField.doc_)) {
753                             String fqName = pkgDiff.name_ + "." + classDiff.name_;
754                             String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
755                             String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newField.name_ + "\" class=\"hiddenlink\">";
756                             String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + newField.name_;
757                             String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
758                                 link2 + HTMLReportGenerator.simpleName(memberDiff.newType_) + " <b>" + newField.name_ + "</b></a>";
759                             memberDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldField.doc_, newField.doc_, id, title);
760                             differs = true;
761                         }
762 
763                         // Other differences
764                         String modifiersChange = oldField.modifiers_.diff(newField.modifiers_);
765                         memberDiff.addModifiersChange(modifiersChange);
766                         if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
767                             System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + newClass.name_ + ", field " + newField.name_);
768                         }
769                         if (trace)
770                             System.out.println("    Field " + newField.name_ + " was changed");
771                         classDiff.fieldsChanged.add(memberDiff);
772                         differs = true;
773                     }
774                 } else if (oldField.inheritedFrom_ == null) {
775                     if (trace)
776                         System.out.println("    Field " + oldField.name_ + " was removed");
777                     classDiff.fieldsRemoved.add(oldField);
778                     differs = true;
779                 }
780             }
781         } // while (iter.hasNext())
782 
783         // Find fields which were added in the new class
784         iter = newClass.fields_.iterator();
785         while (iter.hasNext()) {
786             FieldAPI newField = (FieldAPI)(iter.next());
787             // Only concerned with locally defined fields
788             if (newField.inheritedFrom_ != null)
789                 continue;
790             int idx = Collections.binarySearch(oldClass.fields_, newField);
791             if (idx < 0) {
792                 // See comments above
793                 int existsOld = oldClass.fields_.indexOf(newField);
794                 if (existsOld != -1) {
795                     // Don't mark a field as added if it was marked as changed
796                 } else {
797                     if (trace)
798                         System.out.println("    Field " + newField.name_ + " was added");
799                     classDiff.fieldsAdded.add(newField);
800                     differs = true;
801                 }
802             }
803         } // while (iter.hasNext())
804 
805         return differs;
806     } // compareFields()
807 
808     /**
809      * Decide if two blocks of documentation changed.
810      *
811      * @return true if both are non-null and differ,
812      *              or if one is null and the other is not.
813      */
docChanged(String oldDoc, String newDoc)814     public static boolean docChanged(String oldDoc, String newDoc) {
815         if (!HTMLReportGenerator.reportDocChanges)
816             return false; // Don't even count doc changes as changes
817         if (oldDoc == null && newDoc != null)
818             return true;
819         if (oldDoc != null && newDoc == null)
820             return true;
821         if (oldDoc != null && newDoc != null && oldDoc.compareTo(newDoc) != 0)
822             return true;
823         return false;
824     }
825 
826     /**
827      * Decide if two elements changed where they were defined.
828      *
829      * @return 0 if both are null, or both are non-null and are the same.
830      *         1 if the oldInherit was null and newInherit is non-null.
831      *         2 if the oldInherit was non-null and newInherit is null.
832      *         3 if the oldInherit was non-null and newInherit is non-null
833      *           and they differ.
834      */
changedInheritance(String oldInherit, String newInherit)835     public static int changedInheritance(String oldInherit, String newInherit) {
836         if (oldInherit == null && newInherit == null)
837             return 0;
838         if (oldInherit == null && newInherit != null)
839             return 1;
840         if (oldInherit != null && newInherit == null)
841             return 2;
842         if (oldInherit.compareTo(newInherit) == 0)
843             return 0;
844         else
845             return 3;
846     }
847 
848     /**
849      * Generate a link to the Javadoc page for the given method.
850      */
linkToClass(MethodAPI m, boolean useNew)851     public static String linkToClass(MethodAPI m, boolean useNew) {
852         String sig = m.getSignature();
853         if (sig.compareTo("void") == 0)
854             sig = "";
855         return linkToClass(m.inheritedFrom_, m.name_, sig, useNew);
856     }
857 
858     /**
859      * Generate a link to the Javadoc page for the given field.
860      */
linkToClass(FieldAPI m, boolean useNew)861     public static String linkToClass(FieldAPI m, boolean useNew) {
862         return linkToClass(m.inheritedFrom_, m.name_, null, useNew);
863     }
864 
865     /**
866      * Given the name of the class, generate a link to a relevant page.
867      * This was originally for inheritance changes, so the JDiff page could
868      * be a class changes page, or a section in a removed or added classes.
869      * table. Since there was no easy way to tell which type the link
870      * should be, it is now just a link to the relevant Javadoc page.
871      */
linkToClass(String className, String memberName, String memberType, boolean useNew)872     public static String linkToClass(String className, String memberName,
873                                      String memberType, boolean useNew) {
874         if (!useNew && HTMLReportGenerator.oldDocPrefix == null) {
875             return "<code>" + className + "</code>"; // No link possible
876         }
877         API api = oldAPI_;
878         String prefix = HTMLReportGenerator.oldDocPrefix;
879         if (useNew) {
880             api = newAPI_;
881             prefix = HTMLReportGenerator.newDocPrefix;
882         }
883         ClassAPI cls = (ClassAPI)api.classes_.get(className);
884         if (cls == null) {
885             if (useNew)
886                 System.out.println("Warning: class " + className + " not found in the new API when creating Javadoc link");
887             else
888                 System.out.println("Warning: class " + className + " not found in the old API when creating Javadoc link");
889             return "<code>" + className + "</code>";
890         }
891         int clsIdx = className.indexOf(cls.name_);
892         if (clsIdx != -1) {
893             String pkgRef = className.substring(0, clsIdx);
894             pkgRef = pkgRef.replace('.', '/');
895             String res = "<a href=\"" + prefix + pkgRef + cls.name_ + ".html#" + memberName;
896             if (memberType != null)
897                 res += "(" + memberType + ")";
898             res += "\" target=\"_top\">" + "<code>" + cls.name_ + "</code></a>";
899             return res;
900         }
901         return "<code>" + className + "</code>";
902     }
903 
904     /**
905      * Return the number of methods which are locally defined.
906      */
numLocalMethods(List methods)907     public int numLocalMethods(List methods) {
908         int res = 0;
909         Iterator iter = methods.iterator();
910         while (iter.hasNext()) {
911             MethodAPI m = (MethodAPI)(iter.next());
912             if (m.inheritedFrom_ == null)
913                 res++;
914         }
915         return res;
916     }
917 
918     /**
919      * Return the number of fields which are locally defined.
920      */
numLocalFields(List fields)921     public int numLocalFields(List fields) {
922         int res = 0;
923         Iterator iter = fields.iterator();
924         while (iter.hasNext()) {
925             FieldAPI f = (FieldAPI)(iter.next());
926             if (f.inheritedFrom_ == null)
927                 res++;
928         }
929         return res;
930     }
931 
932     /** Set to enable increased logging verbosity for debugging. */
933     private boolean trace = false;
934 }
935