
fun.mike.record.RecordDiffer Maven / Gradle / Ivy
The newest version!
package fun.mike.record;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class RecordDiffer {
public static final class Diff {
public final Record left;
public final Record right;
public List leftOnly;
public List rightOnly;
public List diffs;
private Diff(Record left, Record right) {
this.left = left;
this.right = right;
}
private Diff(Record left,
Record right,
Collection leftOnly,
Collection rightOnly,
Collection diffs) {
this.left = left;
this.right = right;
this.leftOnly = new LinkedList<>(leftOnly);
this.rightOnly = new LinkedList<>(rightOnly);
this.diffs = new LinkedList<>(diffs);
}
public static Diff leftNull(Record right) { return new Diff(null, right); }
public static Diff rightNull(Record left) { return new Diff(left, null); }
public static Diff of(Record left,
Record right,
Collection leftOnly,
Collection rightOnly,
Collection diffs) {
return new Diff(left, right, leftOnly, rightOnly, diffs);
}
@Override
public String toString() {
return "Diff{" +
"left=" + left +
", right=" + right +
", leftOnly=" + leftOnly +
", rightOnly=" + rightOnly +
", diffs=" + diffs +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Diff diff = (Diff) o;
return Objects.equals(left, diff.left) &&
Objects.equals(right, diff.right) &&
Objects.equals(leftOnly, diff.leftOnly) &&
Objects.equals(rightOnly, diff.rightOnly) &&
Objects.equals(diffs, diff.diffs);
}
@Override
public int hashCode() {
return Objects.hash(left, right, leftOnly, rightOnly, diffs);
}
}
public static final class Match {
public final String key;
public final Object left;
public final Object right;
private Match(String key, Object left, Object right) {
this.key = key;
this.left = left;
this.right = right;
}
static Match of(String key, Object left, Object right) {
return new Match(key, left, right);
}
@Override
public String toString() {
return "Match{" +
"key='" + key + '\'' +
", left=" + left +
", right=" + right +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Match match = (Match) o;
return Objects.equals(key, match.key) &&
Objects.equals(left, match.left) &&
Objects.equals(right, match.right);
}
@Override
public int hashCode() {
return Objects.hash(key, left, right);
}
}
public static final class Entry {
public final String key;
public final Object value;
private Entry(String key, Object value) {
this.key = key;
this.value = value;
}
static Entry of(String key, Object value) {
return new Entry(key, value);
}
@Override
public String toString() {
return "Entry{" +
"key='" + key + '\'' +
", value=" + value +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(key, entry.key) &&
Objects.equals(value, entry.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
}
public static Optional diff(Record left, Record right) {
if (left == null && right == null) {
return Optional.empty();
}
if (left == null) {
return Optional.of(Diff.leftNull(right));
}
if (right == null) {
return Optional.of(Diff.rightNull(left));
}
List leftOnly = new LinkedList<>();
List rightOnly = new LinkedList<>();
List diffs = new LinkedList<>();
Set allKeys = new HashSet<>();
allKeys.addAll(left.keySet());
allKeys.addAll(right.keySet());
for (String key : allKeys) {
boolean inLeft = left.containsKey(key);
boolean inRight = right.containsKey(key);
if (inRight && inLeft) {
Object leftValue = left.get(key);
Object rightValue = right.get(key);
if (!Objects.equals(leftValue, rightValue)) {
diffs.add(Match.of(key, leftValue, rightValue));
}
} else if (inRight) {
Object rightValue = right.get(key);
rightOnly.add(Entry.of(key, rightValue));
} else if (inLeft) {
Object leftValue = left.get(key);
leftOnly.add(Entry.of(key, leftValue));
}
}
boolean someOnlyLeft = !leftOnly.isEmpty();
boolean someOnlyRight = !rightOnly.isEmpty();
boolean someDiffs = !diffs.isEmpty();
if (someOnlyLeft || someOnlyRight || someDiffs) {
return Optional.of(Diff.of(left,
right,
leftOnly,
rightOnly,
diffs));
}
return Optional.empty();
}
public static String describe(Diff diff) {
return describe(diff, "Records do not match.");
}
public static String describe(Diff diff, String message) {
Record left = diff.left;
Record right = diff.right;
if(left == null && right == null) {
throw new IllegalStateException("Both records are null.");
}
if(left == null) {
return "Expected record is null, but actual is not.\n\nActual record:\n" + right.code();
}
if(right == null) {
return "Actual record is null, but expected is not.\n\nExpected record:\n" + left.code();
}
List leftOnly = diff.leftOnly;
List rightOnly = diff.rightOnly;
List diffs = diff.diffs;
boolean someOnlyLeft = !leftOnly.isEmpty();
boolean someOnlyRight = !rightOnly.isEmpty();
boolean someDiffs = !diffs.isEmpty();
StringBuilder buffer = new StringBuilder();
buffer.append(String.join("\n",
message + "\n",
"Expected | " + left,
"Actual | " + right));
buffer.append('\n');
if (someOnlyRight) {
buffer.append("\nOnly in actual\n");
buffer.append("--------------\n");
for (Entry entry : rightOnly) {
Object value = entry.value;
String label = valueLabel(value);
buffer.append(String.format("%s=|%s| %s\n",
entry.key,
value,
label));
}
}
if (someOnlyLeft) {
buffer.append("\nOnly in expected\n");
buffer.append("----------------\n");
for (Entry entry : leftOnly) {
Object value = entry.value;
String label = valueLabel(value);
buffer.append(String.format("%s=|%s| %s\n",
entry.key,
value,
label));
}
}
if (someDiffs) {
buffer.append("\nDiffs\n");
buffer.append("-----\n");
buffer.append(diffs.stream()
.map(RecordDiffer::diffText)
.collect(Collectors.joining("\n\n")));
}
return buffer.toString();
}
private static String diffText(Match diff) {
return String.format("%s=|%s| %s\n%s=|%s| %s",
diff.key,
diff.left,
valueLabel(diff.left),
diff.key,
diff.right,
valueLabel(diff.right));
}
private static String valueLabel(Object value) {
if(value == null) {
return "null";
}
return value.getClass().getName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy