// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.history; /// Feel free to move me somewhere else. Maybe a bit specific for josm.tools? import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.openstreetmap.josm.gui.history.TwoColumnDiff.Item.DiffItemType; import org.openstreetmap.josm.tools.Diff; import org.openstreetmap.josm.tools.Utils; /** * Produces a "two column diff" of two lists. (same as diff -y) * * Each list is annotated with the changes relative to the other, and "empty" cells are inserted so the lists are comparable item by item. * * diff on [1 2 3 4] [1 a 4 5] yields: * * item(SAME, 1) item(SAME, 1) * item(CHANGED, 2) item(CHANGED, 2) * item(DELETED, 3) item(EMPTY) * item(SAME, 4) item(SAME, 4) * item(EMPTY) item(INSERTED, 5) * * @author olejorgenb */ class TwoColumnDiff { public static class Item { public enum DiffItemType { INSERTED(new Color(0xDD, 0xFF, 0xDD)), DELETED(new Color(255, 197, 197)), CHANGED(new Color(255, 234, 213)), REVERSED(new Color(255, 255, 204)), SAME(new Color(234, 234, 234)), EMPTY(new Color(234, 234, 234)); private final Color color; DiffItemType(Color color) { this.color = color; } public Color getColor() { return color; } } Item(DiffItemType state, Object value) { this.state = state; this.value = state == DiffItemType.EMPTY ? null : value; } public final Object value; public final DiffItemType state; } public List<Item> referenceDiff; public List<Item> currentDiff; private final Object[] reference; private final Object[] current; boolean referenceReversed; TwoColumnDiff(Object[] reference, Object... current) { this.reference = Utils.copyArray(reference); this.current = Utils.copyArray(current); referenceDiff = new ArrayList<>(); currentDiff = new ArrayList<>(); diff(); } private void diff() { Diff.Change script = new Diff(reference, current).diff2(false); // attempt diff with reference reversed and test whether less deletions+inserts are required Object[] referenceReversed = Utils.copyArray(reference); Collections.reverse(Arrays.asList(referenceReversed)); Diff.Change scriptReversed = new Diff(referenceReversed, current).diff2(false); if (scriptReversed == null /* reference and current are identical */ || (script != null && scriptReversed.getTotalNumberOfChanges() < script.getTotalNumberOfChanges())) { this.referenceReversed = true; twoColumnDiffFromScript(scriptReversed, referenceReversed, current, true); } else { this.referenceReversed = false; twoColumnDiffFromScript(script, reference, current, false); } } /** * The result from the diff algorithm is a "script" (a compressed description of the changes) * This method expands this script into a full two column description. * @param script diff script * @param a reference version * @param b current version * @param reversed if {@code true} use {@link DiffItemType#REVERSED} instead of {@link DiffItemType#SAME} */ private void twoColumnDiffFromScript(Diff.Change script, Object[] a, Object[] b, final boolean reversed) { int ia = 0; int ib = 0; while (script != null) { int deleted = script.deleted; int inserted = script.inserted; while (ia < script.line0 && ib < script.line1) { referenceDiff.add(new Item(reversed ? DiffItemType.REVERSED : DiffItemType.SAME, a[ia++])); currentDiff.add(new Item(DiffItemType.SAME, b[ib++])); } while (inserted > 0 || deleted > 0) { if (inserted > 0 && deleted > 0) { referenceDiff.add(new Item(DiffItemType.CHANGED, a[ia++])); currentDiff.add(new Item(DiffItemType.CHANGED, b[ib++])); } else if (inserted > 0) { referenceDiff.add(new Item(DiffItemType.EMPTY, null)); currentDiff.add(new Item(DiffItemType.INSERTED, b[ib++])); } else { referenceDiff.add(new Item(DiffItemType.DELETED, a[ia++])); currentDiff.add(new Item(DiffItemType.EMPTY, null)); } inserted--; deleted--; } script = script.link; } while (ia < a.length && ib < b.length) { referenceDiff.add(new Item(reversed ? DiffItemType.REVERSED : DiffItemType.SAME, a[ia++])); currentDiff.add(new Item(DiffItemType.SAME, b[ib++])); } } }