All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.incava.util.diff.Diff Maven / Gradle / Ivy

There is a newer version: 1.3.10
Show newest version
package org.incava.util.diff;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;


/**
 * Compares two lists, returning a list of the additions, changes, and deletions
 * between them. A Comparator may be passed as an argument to the
 * constructor, and will thus be used. If not provided, the initial value in the
 * a ("from") list will be looked at to see if it supports the
 * Comparable interface. If so, its equals and
 * compareTo methods will be invoked on the instances in the "from"
 * and "to" lists; otherwise, for speed, hash codes from the objects will be
 * used instead for comparison.
 *
 * 

The file FileDiff.java shows an example usage of this class, in an * application similar to the Unix "diff" program.

*/ @SuppressWarnings("all") public class Diff { /** * The source list, AKA the "from" values. */ protected List a; /** * The target list, AKA the "to" values. */ protected List b; /** * The list of differences, as Difference instances. */ protected List diffs = new ArrayList(); /** * The pending, uncommitted difference. */ private Difference pending; /** * The comparator used, if any. */ private Comparator comparator; /** * The thresholds. */ private TreeMap thresh; /** * Constructs the Diff object for the two arrays, using the given comparator. */ public Diff(Type[] a, Type[] b, Comparator comp) { this(Arrays.asList(a), Arrays.asList(b), comp); } /** * Constructs the Diff object for the two arrays, using the default * comparison mechanism between the objects, such as equals and * compareTo. */ public Diff(Type[] a, Type[] b) { this(a, b, null); } /** * Constructs the Diff object for the two lists, using the given comparator. */ public Diff(List a, List b, Comparator comp) { this.a = a; this.b = b; this.comparator = comp; this.thresh = null; } /** * Constructs the Diff object for the two lists, using the default * comparison mechanism between the objects, such as equals and * compareTo. */ public Diff(List a, List b) { this(a, b, null); } /** * Runs diff and returns the results. */ public List diff() { this.traverseSequences(); // add the last difference, if pending: if (this.pending != null) { this.diffs.add(this.pending); } return this.diffs; } /** * Traverses the sequences, seeking the longest common subsequences, * invoking the methods finishedA, finishedB, * onANotB, and onBNotA. */ protected void traverseSequences() { Integer[] matches = this.getLongestCommonSubsequences(); int lastA = this.a.size() - 1; int lastB = this.b.size() - 1; int bi = 0; int ai; int lastMatch = matches.length - 1; for (ai = 0; ai <= lastMatch; ++ai) { Integer bLine = matches[ai]; if (bLine == null) { this.onANotB(ai, bi); } else { while (bi < bLine) { this.onBNotA(ai, bi++); } this.onMatch(ai, bi++); } } boolean calledFinishA = false; boolean calledFinishB = false; while (ai <= lastA || bi <= lastB) { // last A? if (ai == lastA + 1 && bi <= lastB) { if (!calledFinishA && this.callFinishedA()) { this.finishedA(lastA); calledFinishA = true; } else { while (bi <= lastB) { this.onBNotA(ai, bi++); } } } // last B? if (bi == lastB + 1 && ai <= lastA) { if (!calledFinishB && this.callFinishedB()) { this.finishedB(lastB); calledFinishB = true; } else { while (ai <= lastA) { this.onANotB(ai++, bi); } } } if (ai <= lastA) { this.onANotB(ai++, bi); } if (bi <= lastB) { this.onBNotA(ai, bi++); } } } /** * Override and return true in order to have finishedA invoked * at the last element in the a array. */ protected boolean callFinishedA() { return false; } /** * Override and return true in order to have finishedB invoked * at the last element in the b array. */ protected boolean callFinishedB() { return false; } /** * Invoked at the last element in a, if * callFinishedA returns true. */ protected void finishedA(int lastA) { } /** * Invoked at the last element in b, if * callFinishedB returns true. */ protected void finishedB(int lastB) { } /** * Invoked for elements in a and not in b. */ protected void onANotB(int ai, int bi) { if (this.pending == null) { this.pending = new Difference(ai, ai, bi, -1); } else { this.pending.setDeleted(ai); } } /** * Invoked for elements in b and not in a. */ protected void onBNotA(int ai, int bi) { if (this.pending == null) { this.pending = new Difference(ai, -1, bi, bi); } else { this.pending.setAdded(bi); } } /** * Invoked for elements matching in a and b. */ protected void onMatch(int ai, int bi) { if (this.pending == null) { // no current pending } else { this.diffs.add(this.pending); this.pending = null; } } /** * Compares the two objects, using the comparator provided with the * constructor, if any. */ protected boolean equals(Type x, Type y) { return this.comparator == null ? x.equals(y) : this.comparator.compare(x, y) == 0; } /** * Returns an array of the longest common subsequences. */ public Integer[] getLongestCommonSubsequences() { int aStart = 0; int aEnd = this.a.size() - 1; int bStart = 0; int bEnd = this.b.size() - 1; TreeMap matches = new TreeMap(); while (aStart <= aEnd && bStart <= bEnd && this.equals(this.a.get(aStart), this.b.get(bStart))) { matches.put(aStart++, bStart++); } while (aStart <= aEnd && bStart <= bEnd && this.equals(this.a.get(aEnd), this.b.get(bEnd))) { matches.put(aEnd--, bEnd--); } Map> bMatches = null; if (this.comparator == null) { if (this.a.size() > 0 && this.a.get(0) instanceof Comparable) { // this uses the Comparable interface bMatches = new TreeMap>(); } else { // this just uses hashCode() bMatches = new HashMap>(); } } else { // we don't really want them sorted, but this is the only Map // implementation (as of JDK 1.4) that takes a comparator. bMatches = new TreeMap>(this.comparator); } for (int bi = bStart; bi <= bEnd; ++bi) { Type element = this.b.get(bi); Type key = element; List positions = bMatches.get(key); if (positions == null) { positions = new ArrayList(); bMatches.put(key, positions); } positions.add(bi); } this.thresh = new TreeMap(); Map links = new HashMap(); for (int i = aStart; i <= aEnd; ++i) { Type aElement = this.a.get(i); List positions = bMatches.get(aElement); if (positions != null) { Integer k = 0; ListIterator pit = positions.listIterator(positions.size()); while (pit.hasPrevious()) { Integer j = pit.previous(); k = this.insert(j, k); if (k == null) { // nothing } else { Object value = k > 0 ? links.get(k - 1) : null; links.put(k, new Object[] { value, i, j }); } } } } if (this.thresh.size() > 0) { Integer ti = this.thresh.lastKey(); Object[] link = links.get(ti); while (link != null) { Integer x = (Integer)link[1]; Integer y = (Integer)link[2]; matches.put(x, y); link = (Object[])link[0]; } } int size = matches.size() == 0 ? 0 : 1 + matches.lastKey(); Integer[] ary = new Integer[size]; for (Integer idx : matches.keySet()) { Integer val = matches.get(idx); ary[idx] = val; } return ary; } /** * Returns whether the integer is not zero (including if it is not null). */ protected static boolean isNonzero(Integer i) { return i != null && i != 0; } /** * Returns whether the value in the map for the given index is greater than * the given value. */ protected boolean isGreaterThan(Integer index, Integer val) { Integer lhs = this.thresh.get(index); return lhs != null && val != null && lhs.compareTo(val) > 0; } /** * Returns whether the value in the map for the given index is less than * the given value. */ protected boolean isLessThan(Integer index, Integer val) { Integer lhs = this.thresh.get(index); return lhs != null && (val == null || lhs.compareTo(val) < 0); } /** * Returns the value for the greatest key in the map. */ protected Integer getLastValue() { return this.thresh.get(this.thresh.lastKey()); } /** * Adds the given value to the "end" of the threshold map, that is, with the * greatest index/key. */ protected void append(Integer value) { Integer addIdx = null; if (this.thresh.size() == 0) { addIdx = 0; } else { Integer lastKey = this.thresh.lastKey(); addIdx = lastKey + 1; } this.thresh.put(addIdx, value); } /** * Inserts the given values into the threshold map. */ protected Integer insert(Integer j, Integer k) { if (Diff.isNonzero(k) && this.isGreaterThan(k, j) && this.isLessThan(k - 1, j)) { this.thresh.put(k, j); } else { int high = -1; if (Diff.isNonzero(k)) { high = k; } else if (this.thresh.size() > 0) { high = this.thresh.lastKey(); } // off the end? if (high == -1 || j.compareTo(this.getLastValue()) > 0) { this.append(j); k = high + 1; } else { // binary search for insertion point: int low = 0; while (low <= high) { int index = (high + low) / 2; Integer val = this.thresh.get(index); int cmp = j.compareTo(val); if (cmp == 0) { return null; } else if (cmp > 0) { low = index + 1; } else { high = index - 1; } } this.thresh.put(low, j); k = low; } } return k; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy