package restx.tests.json;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import java.util.*;
import static java.util.Arrays.asList;
import static restx.common.MorePreconditions.checkPresent;
/**
* Date: 2/2/14
* Time: 18:49
*/
@SuppressWarnings("unchecked")
public class JsonDiff {
private final JsonWithLocationsParser.ParsedJsonWithLocations leftObj;
private final JsonWithLocationsParser.ParsedJsonWithLocations rightObj;
private List<Difference> differences = new ArrayList<>();
private List<String> currentLeftPath = new LinkedList<>(asList("."));
private List<String> currentRightPath = new LinkedList<>(asList("."));
private Map<String, Object> leftContexts = new LinkedHashMap<>();
private Map<String, Object> rightContexts = new LinkedHashMap<>();
private Map<String, Setter> leftSetters = new LinkedHashMap<>();
private Map<String, Setter> rightSetters = new LinkedHashMap<>();
public JsonDiff(JsonWithLocationsParser.ParsedJsonWithLocations leftObj,
JsonWithLocationsParser.ParsedJsonWithLocations rightObj) {
this.leftObj = leftObj;
this.rightObj = rightObj;
putContexts(new NoSetter(), leftObj.getRoot(), new NoSetter(), rightObj.getRoot());
}
public JsonWithLocationsParser.ParsedJsonWithLocations getLeftObj() {
return leftObj;
}
public JsonWithLocationsParser.ParsedJsonWithLocations getRightObj() {
return rightObj;
}
public boolean isSame() {
return differences.isEmpty();
}
protected JsonDiff goIn(String path) {
currentLeftPath.add(path);
currentRightPath.add(path);
return this;
}
public JsonDiff goIn(String left, String right) {
currentLeftPath.add(left);
currentRightPath.add(right);
return this;
}
protected JsonDiff goUp() {
currentLeftPath.remove(currentLeftPath.size() - 1);
currentRightPath.remove(currentRightPath.size() - 1);
return this;
}
protected String currentLeftPath() {
return pathFor(currentLeftPath);
}
protected String currentRightPath() {
return pathFor(currentRightPath);
}
public JsonDiff addDifference(Difference difference) {
differences.add(difference);
return this;
}
public ImmutableList<Difference> getDifferences() {
return ImmutableList.copyOf(differences);
}
protected JsonObjectLocation contextLeft(Object o) {
return checkPresent(leftObj.getLocations().getLocationOf(o),
"can't find left context for " + o);
}
protected JsonObjectLocation contextRight(Object o) {
return checkPresent(rightObj.getLocations().getLocationOf(o),
"can't find right context for " + o);
}
public JsonDiff putContexts(Setter leftSetter, Object left, Setter rightSetter, Object right) {
leftContexts.put(currentLeftPath(), left);
leftSetters.put(currentLeftPath(), leftSetter);
rightContexts.put(currentRightPath(), right);
rightSetters.put(currentRightPath(), rightSetter);
return this;
}
public JsonObjectLocation currentLeftContextLocation() {
List<String> path = new LinkedList<>(this.currentLeftPath);
Optional<JsonObjectLocation> l = leftObj.getLocations().getLocationOf(leftContexts.get(pathFor(path)));
while (!l.isPresent() && path.size() > 1) {
path.remove(path.size() - 1);
l = leftObj.getLocations().getLocationOf(leftContexts.get(pathFor(path)));
}
return l.orNull();
}
public JsonObjectLocation currentRightContextLocation() {
List<String> path = new LinkedList<>(this.currentRightPath);
Optional<JsonObjectLocation> l = rightObj.getLocations().getLocationOf(rightContexts.get(pathFor(path)));
while (!l.isPresent() && path.size() > 1) {
path.remove(path.size() - 1);
l = rightObj.getLocations().getLocationOf(rightContexts.get(pathFor(path)));
}
return l.orNull();
}
private String pathFor(List<String> path) {
return Joiner.on("/").join(path);
}
Object getLeftAt(String path) {
return leftContexts.get(path);
}
private void setLeftAt(String path, Object value) {
leftSetters.get(path).set(value);
}
Object getRightAt(String path) {
return rightContexts.get(path);
}
private void setRightAt(String path, Object value) {
rightSetters.get(path).set(value);
}
String getParentPath(String path) {
int i = path.lastIndexOf('/');
return i == -1 ? path : path.substring(0, i);
}
String getLastElementPath(String path) {
return path.substring(path.lastIndexOf('/') + 1);
}
public static interface Difference {
public abstract String getType();
public String getLeftPath();
public String getRightPath();
public JsonObjectLocation getLeftContext();
public JsonObjectLocation getRightContext();
void mergeToRight(JsonDiff diff);
void mergeToLeft(JsonDiff diff);
}
public static abstract class AbstractDiff implements Difference {
private final String leftPath;
private final String rightPath;
private final JsonObjectLocation leftContext;
private final JsonObjectLocation rightContext;
protected AbstractDiff(String leftPath, String rightPath, JsonObjectLocation leftContext, JsonObjectLocation rightContext) {
this.leftPath = leftPath;
this.rightPath = rightPath;
this.leftContext = leftContext;
this.rightContext = rightContext;
}
public abstract String getType();
public String getLeftPath() {
return leftPath;
}
public String getRightPath() {
return rightPath;
}
public JsonObjectLocation getLeftContext() {
return leftContext;
}
public JsonObjectLocation getRightContext() {
return rightContext;
}
@Override
public void mergeToRight(JsonDiff diff) {
throw new UnsupportedOperationException();
}
@Override
public void mergeToLeft(JsonDiff diff) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "Diff{" +
"leftPath='" + leftPath + '\'' +
", rightPath='" + rightPath + '\'' +
", leftContext=" + leftContext +
", rightContext=" + rightContext +
'}';
}
}
public static class ValueDiff extends AbstractDiff {
private final Object leftValue;
private final Object rightValue;
public ValueDiff(String leftPath, String rightPath,
JsonObjectLocation leftContext, JsonObjectLocation rightContext,
Object leftValue, Object rightValue) {
super(leftPath, rightPath, leftContext, rightContext);
this.leftValue = leftValue;
this.rightValue = rightValue;
}
public String getType() {
return "CHANGED";
}
public Object getLeftValue() {
return leftValue;
}
public Object getRightValue() {
return rightValue;
}
@Override
public void mergeToRight(JsonDiff diff) {
diff.setRightAt(getRightPath(), leftValue);
}
@Override
public void mergeToLeft(JsonDiff diff) {
diff.setLeftAt(getLeftPath(), rightValue);
}
@Override
public String toString() {
return "ValueDiff{" + super.toString() +
", leftValue=" + leftValue +
", rightValue=" + rightValue +
'}';
}
}
public static abstract class ArrayDiff extends AbstractDiff {
private final int leftPosition;
private final int rightPosition;
private final List<Object> values;
public ArrayDiff(String leftPath, String rightPath, JsonObjectLocation leftContext, JsonObjectLocation rightContext,
int leftPosition, int rightPosition, List<Object> values) {
super(leftPath, rightPath, leftContext, rightContext);
this.leftPosition = leftPosition;
this.rightPosition = rightPosition;
this.values = new ArrayList<>(values);
}
public abstract String getType();
public int getLeftPosition() {
return leftPosition;
}
public int getRightPosition() {
return rightPosition;
}
public List<Object> getValues() {
return values;
}
@Override
public String toString() {
return "ArrayDiff{" + super.toString() +
", type=" + getType() +
", leftPosition=" + leftPosition +
", rightPosition=" + rightPosition +
", values=" + values +
'}';
}
}
public static class ArrayInsertedValue extends ArrayDiff {
public ArrayInsertedValue(String leftPath, String rightPath,
JsonObjectLocation leftContext, JsonObjectLocation rightContext,
int leftPosition, int rightPosition, List<Object> values) {
super(leftPath, rightPath, leftContext, rightContext, leftPosition, rightPosition, values);
}
public String getType() {
return "INSERTED";
}
@Override
public void mergeToRight(JsonDiff diff) {
List l = (List) diff.getRightAt(getRightPath());
for (int i = 0; i < getValues().size(); i++) {
l.remove(getRightPosition());
}
}
@Override
public void mergeToLeft(JsonDiff diff) {
List l = (List) diff.getLeftAt(getLeftPath());
l.addAll(getLeftPosition(), getValues());
}
}
public static class ArrayDeletedValue extends ArrayDiff {
public ArrayDeletedValue(String leftPath, String rightPath,
JsonObjectLocation leftContext, JsonObjectLocation rightContext,
int leftPosition, int rightPosition, List<Object> values) {
super(leftPath, rightPath, leftContext, rightContext, leftPosition, rightPosition, values);
}
public String getType() {
return "DELETED";
}
@Override
public void mergeToRight(JsonDiff diff) {
List l = (List) diff.getRightAt(getRightPath());
l.addAll(getRightPosition(), getValues());
}
@Override
public void mergeToLeft(JsonDiff diff) {
List l = (List) diff.getLeftAt(getLeftPath());
for (int i = 0; i < getValues().size(); i++) {
l.remove(getLeftPosition());
}
}
}
public static abstract class KeyDiff extends AbstractDiff {
private final String key;
private final Object value;
protected KeyDiff(String leftPath, String rightPath, JsonObjectLocation leftContext, JsonObjectLocation rightContext,
String key, Object value) {
super(leftPath, rightPath, leftContext, rightContext);
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "KeyDiff{" + super.toString() +
", type='" + getType() + '\'' +
", key='" + key + '\'' +
", value=" + value +
'}';
}
protected Map<String, Object> getLeftParent(JsonDiff diff) {
return (Map<String, Object>) diff.getLeftAt(getLeftPath());
}
protected Map<String, Object> getRightParent(JsonDiff diff) {
return (Map<String, Object>) diff.getRightAt(getLeftPath());
}
}
public static class AddedKey extends KeyDiff {
public AddedKey(String leftPath, String rightPath, JsonObjectLocation leftContext, JsonObjectLocation rightContext,
String key, Object value) {
super(leftPath, rightPath, leftContext, rightContext, key, value);
}
public String getType() {
return "ADDED";
}
@Override
public void mergeToRight(JsonDiff diff) {
getRightParent(diff).remove(getKey());
}
@Override
public void mergeToLeft(JsonDiff diff) {
getLeftParent(diff).put(getKey(), getValue());
}
}
public static class RemovedKey extends KeyDiff {
public RemovedKey(String leftPath, String rightPath, JsonObjectLocation leftContext, JsonObjectLocation rightContext,
String key, Object value) {
super(leftPath, rightPath, leftContext, rightContext, key, value);
}
public String getType() {
return "REMOVED";
}
@Override
public void mergeToRight(JsonDiff diff) {
getRightParent(diff).put(getKey(), getValue());
}
@Override
public void mergeToLeft(JsonDiff diff) {
getLeftParent(diff).remove(getKey());
}
}
public static interface Setter {
void set(Object value);
}
public static class NoSetter implements Setter {
@Override
public void set(Object value) {
throw new UnsupportedOperationException();
}
}
public static class ListSetter implements Setter {
private final List<Object> objects;
private final int i;
public ListSetter(List<Object> objects, int i) {
this.objects = objects;
this.i = i;
}
@Override
public void set(Object value) {
objects.set(i, value);
}
}
public static class MapSetter implements Setter {
private final Map<String, Object> map;
private final String key;
public MapSetter(Map<String, Object> map, String key) {
this.map = map;
this.key = key;
}
@Override
public void set(Object value) {
map.put(key, value);
}
}
}