001package daikon.diff;
002
003import static java.util.logging.Level.FINE;
004import static java.util.logging.Level.INFO;
005
006import daikon.Daikon;
007import daikon.FileIO;
008import daikon.Ppt;
009import daikon.PptConditional;
010import daikon.PptMap;
011import daikon.PptTopLevel;
012import daikon.inv.Invariant;
013import gnu.getopt.Getopt;
014import gnu.getopt.LongOpt;
015import java.io.File;
016import java.io.FileNotFoundException;
017import java.io.IOException;
018import java.io.OptionalDataException;
019import java.io.StreamCorruptedException;
020import java.lang.reflect.InvocationTargetException;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.Iterator;
025import java.util.List;
026import java.util.NavigableSet;
027import java.util.TreeSet;
028import java.util.logging.Logger;
029import org.checkerframework.checker.initialization.qual.UnknownInitialization;
030import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
031import org.checkerframework.checker.nullness.qual.Nullable;
032import org.checkerframework.checker.signature.qual.ClassGetName;
033import org.plumelib.util.CollectionsPlume;
034import org.plumelib.util.FilesPlume;
035import org.plumelib.util.MPair;
036import org.plumelib.util.OrderedPairIterator;
037import org.plumelib.util.StringsPlume;
038
039/**
040 * Diff is the main class for the invariant diff program. The invariant diff program outputs the
041 * differences between two sets of invariants.
042 *
043 * <p>The following is a high-level description of the program. Each input file contains a
044 * serialized PptMap or InvMap. PptMap and InvMap are similar structures, in that they both map
045 * program points to invariants. However, PptMaps are much more complicated than InvMaps. PptMaps
046 * are output by Daikon, and InvMaps are output by this program.
047 *
048 * <p>First, if either input is a PptMap, it is converted to an InvMap. Next, the two InvMaps are
049 * combined to form a tree. The tree is exactly three levels deep. The first level contains the
050 * root, which holds no data. Each node in the second level is a pair of Ppts, and each node in the
051 * third level is a pair of Invariants. The tree is constructed by pairing the corresponding Ppts
052 * and Invariants in the two PptMaps. Finally, the tree is traversed via the Visitor pattern to
053 * produce output. The Visitor pattern makes it easy to extend the program, simply by writing a new
054 * Visitor.
055 */
056public final class Diff {
057
058  /** Debug logger. */
059  public static final Logger debug = Logger.getLogger("daikon.diff.Diff");
060
061  /** The usage message for this program. */
062  private static String usage =
063      StringsPlume.joinLines(
064          "Usage:",
065          "    java daikon.diff.Diff [flags...] file1 [file2]",
066          "  file1 and file2 are serialized invariants produced by Daikon.",
067          "  If file2 is not specified, file1 is compared with the empty set.",
068          "  For a list of flags, see the Daikon manual, which appears in the ",
069          "  Daikon distribution and also at http://plse.cs.washington.edu/daikon/.");
070
071  /** The long command line options. */
072  private static final String HELP_SWITCH = "help";
073
074  private static final String INV_SORT_COMPARATOR1_SWITCH = "invSortComparator1";
075  private static final String INV_SORT_COMPARATOR2_SWITCH = "invSortComparator2";
076  private static final String INV_PAIR_COMPARATOR_SWITCH = "invPairComparator";
077  private static final String IGNORE_UNJUSTIFIED_SWITCH = "ignore_unjustified";
078  private static final String IGNORE_NUMBERED_EXITS_SWITCH = "ignore_exitNN";
079
080  /** Determine which ppts should be paired together in the tree. */
081  private static final Comparator<PptTopLevel> PPT_COMPARATOR = new Ppt.NameComparator();
082
083  /**
084   * Comparators to sort the sets of invs, and to combine the two sets into the pair tree. Can be
085   * overriden by command-line options.
086   */
087  private Comparator<Invariant> invSortComparator1;
088
089  private Comparator<Invariant> invSortComparator2;
090  private Comparator<Invariant> invPairComparator;
091
092  private boolean examineAllPpts;
093  private boolean ignoreNumberedExits;
094
095  public Diff() {
096    this(false, false);
097  }
098
099  public Diff(boolean examineAllPpts) {
100    this(examineAllPpts, false);
101  }
102
103  public Diff(boolean examineAllPpts, Comparator<Invariant> c) {
104    this(examineAllPpts, false);
105    setAllInvComparators(c);
106  }
107
108  public Diff(boolean examineAllPpts, boolean ignoreNumberedExits) {
109    this.examineAllPpts = examineAllPpts;
110    this.ignoreNumberedExits = ignoreNumberedExits;
111    setAllInvComparators(new Invariant.ClassVarnameComparator());
112  }
113
114  public Diff(
115      boolean examineAllPpts,
116      boolean ignoreNumberedExits,
117      @Nullable @ClassGetName String invSortComparator1Classname,
118      @Nullable @ClassGetName String invSortComparator2Classname,
119      @Nullable @ClassGetName String invPairComparatorClassname,
120      Comparator<Invariant> defaultComparator)
121      throws ClassNotFoundException,
122          IllegalAccessException,
123          InstantiationException,
124          InvocationTargetException,
125          NoSuchMethodException {
126    this.examineAllPpts = examineAllPpts;
127    this.ignoreNumberedExits = ignoreNumberedExits;
128    this.invSortComparator1 = selectComparator(invSortComparator1Classname, defaultComparator);
129    this.invSortComparator2 = selectComparator(invSortComparator2Classname, defaultComparator);
130    this.invPairComparator = selectComparator(invPairComparatorClassname, defaultComparator);
131  }
132
133  /**
134   * Read two PptMap or InvMap objects from their respective files. Convert the PptMaps to InvMaps
135   * as necessary, and diff the InvMaps.
136   */
137  public static void main(String[] args)
138      throws FileNotFoundException,
139          StreamCorruptedException,
140          OptionalDataException,
141          IOException,
142          ClassNotFoundException,
143          IllegalAccessException,
144          InstantiationException,
145          InvocationTargetException,
146          NoSuchMethodException {
147    try {
148      mainHelper(args);
149    } catch (Daikon.DaikonTerminationException e) {
150      daikon.Daikon.handleDaikonTerminationException(e);
151    }
152  }
153
154  /**
155   * This does the work of {@link #main(String[])}, but it never calls System.exit, so it is
156   * appropriate to be called progrmmatically.
157   */
158  public static void mainHelper(final String[] args)
159      throws FileNotFoundException,
160          StreamCorruptedException,
161          OptionalDataException,
162          IOException,
163          ClassNotFoundException,
164          InstantiationException,
165          IllegalAccessException,
166          InvocationTargetException,
167          NoSuchMethodException {
168    daikon.LogHelper.setupLogs(INFO);
169
170    boolean printDiff = false;
171    boolean printAll = false;
172    boolean includeUnjustified = true;
173    boolean stats = false;
174    boolean tabSeparatedStats = false;
175    boolean minus = false;
176    boolean xor = false;
177    boolean union = false;
178    boolean examineAllPpts = false;
179    boolean ignoreNumberedExits = false;
180    boolean printEmptyPpts = false;
181    boolean verbose = false;
182    boolean continuousJustification = false;
183    boolean logging = false;
184    File outputFile = null;
185    @ClassGetName String invSortComparator1Classname = null;
186    @ClassGetName String invSortComparator2Classname = null;
187    @ClassGetName String invPairComparatorClassname = null;
188
189    boolean optionSelected = false;
190
191    daikon.LogHelper.setupLogs(INFO);
192    //     daikon.LogHelper.setLevel ("daikon.diff", FINE);
193
194    LongOpt[] longOpts =
195        new LongOpt[] {
196          new LongOpt(HELP_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
197          new LongOpt(INV_SORT_COMPARATOR1_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
198          new LongOpt(INV_SORT_COMPARATOR2_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
199          new LongOpt(INV_PAIR_COMPARATOR_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
200          new LongOpt(IGNORE_UNJUSTIFIED_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
201          new LongOpt(IGNORE_NUMBERED_EXITS_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
202        };
203
204    Getopt g =
205        new Getopt(
206            "daikon.diff.Diff", args,
207            "Hhyduastmxno:jzpevl", longOpts);
208    int c;
209    while ((c = g.getopt()) != -1) {
210      switch (c) {
211        case 0:
212          // got a long option
213          String optionName = longOpts[g.getLongind()].getName();
214          if (Daikon.help_SWITCH.equals(optionName)) {
215            System.out.println(usage);
216            throw new Daikon.NormalTermination();
217          } else if (INV_SORT_COMPARATOR1_SWITCH.equals(optionName)) {
218            if (invSortComparator1Classname != null) {
219              throw new Error(
220                  "multiple --"
221                      + INV_SORT_COMPARATOR1_SWITCH
222                      + " classnames supplied on command line");
223            }
224            @SuppressWarnings("signature") // user input, should be checked
225            @ClassGetName String cgn = Daikon.getOptarg(g);
226            invSortComparator1Classname = cgn;
227          } else if (INV_SORT_COMPARATOR2_SWITCH.equals(optionName)) {
228            if (invSortComparator2Classname != null) {
229              throw new Error(
230                  "multiple --"
231                      + INV_SORT_COMPARATOR2_SWITCH
232                      + " classnames supplied on command line");
233            }
234            @SuppressWarnings("signature") // user input, should be checked
235            @ClassGetName String cgn = Daikon.getOptarg(g);
236            invSortComparator2Classname = cgn;
237          } else if (INV_PAIR_COMPARATOR_SWITCH.equals(optionName)) {
238            if (invPairComparatorClassname != null) {
239              throw new Error(
240                  "multiple --"
241                      + INV_PAIR_COMPARATOR_SWITCH
242                      + " classnames supplied on command line");
243            }
244            @SuppressWarnings("signature") // user input, should be checked
245            @ClassGetName String cgn = Daikon.getOptarg(g);
246            invPairComparatorClassname = cgn;
247          } else if (IGNORE_UNJUSTIFIED_SWITCH.equals(optionName)) {
248            optionSelected = true;
249            includeUnjustified = false;
250            break;
251          } else if (IGNORE_NUMBERED_EXITS_SWITCH.equals(optionName)) {
252            ignoreNumberedExits = true;
253            break;
254          } else {
255            throw new RuntimeException("Unknown long option received: " + optionName);
256          }
257          break;
258        case 'h':
259          System.out.println(usage);
260          throw new Daikon.NormalTermination();
261        case 'H':
262          PrintAllVisitor.HUMAN_OUTPUT = true;
263          break;
264        case 'y': // included for legacy code
265          optionSelected = true;
266          includeUnjustified = false;
267          break;
268        case 'd':
269          optionSelected = true;
270          printDiff = true;
271          break;
272        case 'a':
273          optionSelected = true;
274          printAll = true;
275          break;
276        case 's':
277          optionSelected = true;
278          stats = true;
279          break;
280        case 't':
281          optionSelected = true;
282          tabSeparatedStats = true;
283          break;
284        case 'm':
285          optionSelected = true;
286          minus = true;
287          break;
288        case 'x':
289          optionSelected = true;
290          xor = true;
291          break;
292        case 'n':
293          optionSelected = true;
294          union = true;
295          break;
296        case 'o':
297          if (outputFile != null) {
298            throw new Error("multiple output files supplied on command line");
299          }
300          String outputFilename = Daikon.getOptarg(g);
301          outputFile = new File(outputFilename);
302          if (!FilesPlume.canCreateAndWrite(outputFile)) {
303            throw new Error("Cannot write to file " + outputFile);
304          }
305          break;
306        case 'j':
307          continuousJustification = true;
308          break;
309        case 'p':
310          examineAllPpts = true;
311          break;
312        case 'e':
313          printEmptyPpts = true;
314          break;
315        case 'v':
316          verbose = true;
317          break;
318        case 'l':
319          logging = true;
320          break;
321        case '?':
322          // getopt() already printed an error
323          System.out.println(usage);
324          throw new Daikon.UserError("Bad argument");
325        default:
326          System.out.println("getopt() returned " + c);
327          break;
328      }
329    }
330
331    // Turn on the defaults
332    if (!optionSelected) {
333      printDiff = true;
334    }
335
336    if (logging) {
337      System.err.println("Invariant Diff: Starting Log");
338    }
339
340    if (logging) {
341      System.err.println("Invariant Diff: Creating Diff Object");
342    }
343
344    Comparator<Invariant> defaultComparator;
345    if (minus || xor || union) {
346      defaultComparator = new Invariant.ClassVarnameFormulaComparator();
347    } else {
348      defaultComparator = new Invariant.ClassVarnameComparator();
349    }
350
351    // Set the comparators based on the command-line options
352
353    Diff diff =
354        new Diff(
355            examineAllPpts,
356            ignoreNumberedExits,
357            invSortComparator1Classname,
358            invSortComparator2Classname,
359            invPairComparatorClassname,
360            defaultComparator);
361
362    if (!diff.invSortComparator1
363            .getClass()
364            .toString()
365            .equals(diff.invSortComparator2.getClass().toString())
366        || !diff.invSortComparator1
367            .getClass()
368            .toString()
369            .equals(diff.invPairComparator.getClass().toString())) {
370      System.out.println("You are using different comparators to sort or pair up invariants.");
371      System.out.println("This may cause misalignment of invariants and may cause Diff to");
372      System.out.println("work incorectly.  Make sure you know what you are doing!");
373    }
374
375    // The index of the first non-option argument -- the name of the
376    // first file
377    int firstFileIndex = g.getOptind();
378    int numFiles = args.length - firstFileIndex;
379
380    InvMap invMap1 = null;
381    InvMap invMap2 = null;
382
383    if (logging) {
384      System.err.println("Invariant Diff: Reading Files");
385    }
386
387    if (numFiles == 1) {
388      String filename1 = args[firstFileIndex];
389      invMap1 = diff.readInvMap(new File(filename1));
390      invMap2 = new InvMap();
391    } else if (numFiles == 2) {
392      String filename1 = args[firstFileIndex];
393      String filename2 = args[firstFileIndex + 1];
394      invMap1 = diff.readInvMap(new File(filename1));
395      invMap2 = diff.readInvMap(new File(filename2));
396    } else if (numFiles > 2) {
397
398      // The new stuff that allows multiple files -LL
399
400      PptMap[] mapAr = new PptMap[numFiles];
401      int j = 0;
402      for (int i = firstFileIndex; i < args.length; i++) {
403        String fileName = args[i];
404        mapAr[j++] = FileIO.read_serialized_pptmap(new File(fileName), false);
405      }
406
407      // Cascade a lot of the different invariants into one map,
408      // and then put them into map1, map2
409
410      // Initialize it all
411      MultiDiffVisitor v1 = new MultiDiffVisitor(mapAr[0]);
412
413      for (int i = 1; i < mapAr.length; i++) {
414        RootNode root = diff.diffPptMap(mapAr[i], v1.currMap, includeUnjustified);
415        root.accept(v1);
416      }
417
418      // now take the final result for the MultiDiffVisitor
419      // and use it along side a null empty map
420      // PptMap map1 = v1.currMap;
421
422      v1.printAll();
423      return;
424    } else {
425      System.out.println(usage);
426      throw new Daikon.NormalTermination();
427    }
428
429    if (logging) {
430      System.err.println("Invariant Diff: Creating Tree");
431    }
432
433    if (logging) {
434      System.err.println("Invariant Diff: Visiting Tree");
435    }
436
437    RootNode root = diff.diffInvMap(invMap1, invMap2, includeUnjustified);
438
439    if (stats) {
440      DetailedStatisticsVisitor v = new DetailedStatisticsVisitor(continuousJustification);
441      root.accept(v);
442      System.out.print(v.format());
443    }
444
445    if (tabSeparatedStats) {
446      DetailedStatisticsVisitor v = new DetailedStatisticsVisitor(continuousJustification);
447      root.accept(v);
448      System.out.print(v.repr());
449    }
450
451    if (printDiff) {
452      PrintDifferingInvariantsVisitor v =
453          new PrintDifferingInvariantsVisitor(System.out, verbose, printEmptyPpts);
454      root.accept(v);
455    }
456
457    if (printAll) {
458      PrintAllVisitor v = new PrintAllVisitor(System.out, verbose, printEmptyPpts);
459      root.accept(v);
460    }
461
462    if (minus) {
463      if (outputFile != null) {
464        MinusVisitor v = new MinusVisitor();
465        root.accept(v);
466        FilesPlume.writeObject(v.getResult(), outputFile);
467        // System.out.println("Output written to: " + outputFile);
468      } else {
469        throw new Error("no output file specified on command line");
470      }
471    }
472
473    if (xor) {
474      if (outputFile != null) {
475        XorVisitor v = new XorVisitor();
476        root.accept(v);
477        InvMap resultMap = v.getResult();
478        FilesPlume.writeObject(resultMap, outputFile);
479        if (debug.isLoggable(FINE)) {
480          debug.fine("Result: " + resultMap.toString());
481        }
482
483        // System.out.println("Output written to: " + outputFile);
484      } else {
485        throw new Error("no output file specified on command line");
486      }
487    }
488
489    if (union) {
490      if (outputFile != null) {
491        UnionVisitor v = new UnionVisitor();
492        root.accept(v);
493        FilesPlume.writeObject(v.getResult(), outputFile);
494        // System.out.println("Output written to: " + outputFile);
495      } else {
496        throw new Error("no output file specified on command line");
497      }
498    }
499
500    if (logging) {
501      System.err.println("Invariant Diff: Ending Log");
502    }
503
504    // finished; return (and end program)
505  }
506
507  /**
508   * Reads an InvMap from a file that contains a serialized InvMap or PptMap.
509   *
510   * @param file a file
511   * @return an InvMap read from the file
512   * @throws IOException if there is trouble reading the file
513   * @throws ClassNotFoundException if an object in the serialized file has an unloadable class
514   */
515  private InvMap readInvMap(File file) throws IOException, ClassNotFoundException {
516    Object o = FilesPlume.readObject(file);
517    if (o instanceof InvMap) {
518      return (InvMap) o;
519    } else {
520      PptMap pptMap = FileIO.read_serialized_pptmap(file, false);
521      return convertToInvMap(pptMap);
522    }
523  }
524
525  /**
526   * Extracts the PptTopLevel and Invariants out of a pptMap, and places them into an InvMap. Maps
527   * PptTopLevel to a List of Invariants. The InvMap is a cleaner representation than the PptMap,
528   * and it allows us to more easily manipulate the contents. The InvMap contains exactly the
529   * elements contained in the PptMap. Conditional program points are also added as keys. Filtering
530   * is done when creating the pair tree. The ppts in the InvMap must be sorted, but the invariants
531   * need not be sorted.
532   */
533  public InvMap convertToInvMap(PptMap pptMap) {
534    InvMap map = new InvMap();
535
536    // Created sorted set of top level ppts, possibly including
537    // conditional ppts
538    NavigableSet<PptTopLevel> ppts = new TreeSet<>(PPT_COMPARATOR);
539    ppts.addAll(pptMap.asCollection());
540
541    for (PptTopLevel ppt : ppts) {
542      if (ignoreNumberedExits && ppt.ppt_name.isNumberedExitPoint()) {
543        continue;
544      }
545
546      // List<Invariant> invs = ppt.getInvariants();
547      List<Invariant> invs = CollectionsPlume.sortList(ppt.getInvariants(), PptTopLevel.icfp);
548      map.put(ppt, invs);
549      if (examineAllPpts) {
550        // Add conditional ppts
551        for (PptConditional pptCond : ppt.cond_iterable()) {
552          List<Invariant> invsCond =
553              CollectionsPlume.sortList(pptCond.getInvariants(), PptTopLevel.icfp);
554          // List<Invariant> invsCond = pptCond.getInvariants();
555          map.put(pptCond, invsCond);
556        }
557      }
558    }
559    return map;
560  }
561
562  /**
563   * Returns a pair tree of corresponding program points, and corresponding invariants at each
564   * program point. This tree can be walked to determine differences between the sets of invariants.
565   * Calls diffInvMap and asks to include all justified invariants.
566   */
567  public RootNode diffInvMap(InvMap map1, InvMap map2) {
568    return diffInvMap(map1, map2, true);
569  }
570
571  /**
572   * Returns a pair tree of corresponding program points, and corresponding invariants at each
573   * program point. This tree can be walked to determine differences between the sets of invariants.
574   * The tree consists of the invariants in map1 and map2. If includeUnjustified is true, the
575   * unjustified invariants are included.
576   */
577  public RootNode diffInvMap(InvMap map1, InvMap map2, boolean includeUnjustified) {
578    RootNode root = new RootNode();
579
580    Iterator<MPair<@Nullable PptTopLevel, @Nullable PptTopLevel>> opi =
581        new OrderedPairIterator<PptTopLevel>(
582            map1.pptSortedIterator(PPT_COMPARATOR),
583            map2.pptSortedIterator(PPT_COMPARATOR),
584            PPT_COMPARATOR);
585    while (opi.hasNext()) {
586      MPair<@Nullable PptTopLevel, @Nullable PptTopLevel> ppts = opi.next();
587      PptTopLevel ppt1 = ppts.first;
588      PptTopLevel ppt2 = ppts.second;
589      if (shouldAdd(ppt1) || shouldAdd(ppt2)) {
590        PptNode node = diffPptTopLevel(ppt1, ppt2, map1, map2, includeUnjustified);
591        root.add(node);
592      }
593    }
594
595    return root;
596  }
597
598  /**
599   * Diffs two PptMaps by converting them to InvMaps. Provided for compatibiliy with legacy code.
600   * Calls diffPptMap and asks to include all invariants.
601   */
602  public RootNode diffPptMap(PptMap pptMap1, PptMap pptMap2) {
603    return diffPptMap(pptMap1, pptMap2, true);
604  }
605
606  /**
607   * Diffs two PptMaps by converting them to InvMaps. Provided for compatibiliy with legacy code. If
608   * includeUnjustified is true, the unjustified invariants are included.
609   */
610  public RootNode diffPptMap(PptMap pptMap1, PptMap pptMap2, boolean includeUnjustified) {
611    InvMap map1 = convertToInvMap(pptMap1);
612    InvMap map2 = convertToInvMap(pptMap2);
613    return diffInvMap(map1, map2, includeUnjustified);
614  }
615
616  /** Returns true if the program point should be added to the tree, false otherwise. */
617  private boolean shouldAdd(@Nullable PptTopLevel ppt) {
618    if (examineAllPpts) {
619      return true;
620    } else {
621      if (ppt == null) {
622        return false;
623      } else if (ppt.ppt_name.isEnterPoint()) {
624        return true;
625      } else if (ppt.ppt_name.isCombinedExitPoint()) {
626        return true;
627      } else {
628        return false;
629      }
630    }
631  }
632
633  /**
634   * Takes a pair of corresponding top-level program points and maps, and returns a tree of the
635   * corresponding invariants. Either of the program points may be null. If includeUnjustied is
636   * true, the unjustified invariants are included.
637   */
638  private PptNode diffPptTopLevel(
639      @Nullable PptTopLevel ppt1,
640      @Nullable PptTopLevel ppt2,
641      InvMap map1,
642      InvMap map2,
643      boolean includeUnjustified) {
644    PptNode pptNode = new PptNode(ppt1, ppt2);
645
646    assert ppt1 == null || ppt2 == null || PPT_COMPARATOR.compare(ppt1, ppt2) == 0
647        : "Program points do not correspond";
648
649    List<Invariant> invs1;
650    if (ppt1 != null) {
651      invs1 = map1.get(ppt1);
652      Collections.sort(invs1, invSortComparator1);
653    } else {
654      invs1 = new ArrayList<Invariant>();
655    }
656
657    List<Invariant> invs2;
658    if (ppt2 != null) {
659      invs2 = map2.get(ppt2);
660      Collections.sort(invs2, invSortComparator2);
661    } else {
662      invs2 = new ArrayList<Invariant>();
663    }
664
665    Iterator<MPair<@Nullable Invariant, @Nullable Invariant>> opi =
666        new OrderedPairIterator<Invariant>(invs1.iterator(), invs2.iterator(), invPairComparator);
667    while (opi.hasNext()) {
668      MPair<@Nullable Invariant, @Nullable Invariant> invariants = opi.next();
669      Invariant inv1 = invariants.first;
670      Invariant inv2 = invariants.second;
671      if (!includeUnjustified) {
672        if ((inv1 != null) && !inv1.justified()) {
673          inv1 = null;
674        }
675        if ((inv2 != null) && !inv2.justified()) {
676          inv2 = null;
677        }
678      }
679      if ((inv1 != null) || (inv2 != null)) {
680        InvNode invNode = new InvNode(inv1, inv2);
681        pptNode.add(invNode);
682      }
683    }
684
685    return pptNode;
686  }
687
688  /** Use the comparator for sorting both sets and creating the pair tree. */
689  @EnsuresNonNull({"invSortComparator1", "invSortComparator2", "invPairComparator"})
690  public void setAllInvComparators(@UnknownInitialization Diff this, Comparator<Invariant> c) {
691    setInvSortComparator1(c);
692    setInvSortComparator2(c);
693    setInvPairComparator(c);
694  }
695
696  /**
697   * If the classname is non-null, returns the comparator named by the classname. Else, returns the
698   * default.
699   */
700  private static Comparator<Invariant> selectComparator(
701      @Nullable @ClassGetName String classname, Comparator<Invariant> defaultComparator)
702      throws ClassNotFoundException,
703          IllegalAccessException,
704          InstantiationException,
705          InvocationTargetException,
706          NoSuchMethodException {
707
708    if (classname != null) {
709      Class<?> cls = Class.forName(classname);
710      @SuppressWarnings("unchecked")
711      Comparator<Invariant> cmp =
712          (Comparator<Invariant>) cls.getDeclaredConstructor().newInstance();
713      return cmp;
714    } else {
715      return defaultComparator;
716    }
717  }
718
719  /** Use the comparator for sorting the first set. */
720  @EnsuresNonNull("invSortComparator1")
721  public void setInvSortComparator1(@UnknownInitialization Diff this, Comparator<Invariant> c) {
722    invSortComparator1 = c;
723  }
724
725  /** Use the comparator for sorting the second set. */
726  @EnsuresNonNull("invSortComparator2")
727  public void setInvSortComparator2(@UnknownInitialization Diff this, Comparator<Invariant> c) {
728    invSortComparator2 = c;
729  }
730
731  /** Use the comparator for creating the pair tree. */
732  @EnsuresNonNull("invPairComparator")
733  public void setInvPairComparator(@UnknownInitialization Diff this, Comparator<Invariant> c) {
734    invPairComparator = c;
735  }
736}