Last active
June 27, 2019 02:47
-
-
Save hank-cp/3db40faed1dd9f02ababd86c2c9eaf8d to your computer and use it in GitHub Desktop.
Javers complex @valueobject comparator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.gson.JsonArray; | |
import com.google.gson.JsonElement; | |
import com.google.gson.JsonObject; | |
import org.javers.core.diff.changetype.map.*; | |
import org.javers.core.diff.custom.CustomPropertyComparator; | |
import org.javers.core.json.JsonConverter; | |
import org.javers.core.metamodel.object.GlobalId; | |
import org.javers.core.metamodel.property.Property; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Objects; | |
import java.util.Stack; | |
import java.util.stream.Collectors; | |
public abstract class NestedObjectComparator<T> implements CustomPropertyComparator<T, MapChange> { | |
private JsonConverter jsonConverter; | |
public void setJsonConverter(JsonConverter jsonConverter) { | |
this.jsonConverter = jsonConverter; | |
} | |
@Override | |
public MapChange compare(T old, T current, | |
GlobalId affectedId, Property property) { | |
assert(jsonConverter != null); | |
JsonElement oldJson = jsonConverter.toJsonElement(old); | |
JsonElement currentJson = jsonConverter.toJsonElement(current); | |
List<EntryChange> changes = parseChanges(oldJson, currentJson); | |
if (changes == null || changes.size <= 0) return null; | |
return new MapChange(affectedId, property.getName(), changes); | |
} | |
private List<EntryChange> parseChanges(JsonElement old, JsonElement current) { | |
List<EntryChange> changes = new ArrayList<>(); | |
Stack<String> fieldPath = new Stack<>(); | |
resolveChanges(old, current, changes, fieldPath); | |
return changes; | |
} | |
private void resolveChanges(JsonElement old, JsonElement current, List<EntryChange> changes, Stack<String> fieldPath) { | |
String path = fieldPath.stream().collect(Collectors.joining(".")); | |
if ((old == null || old.isJsonNull()) | |
&& (current == null || current.isJsonNull())) return; | |
if (old != null && !old.isJsonNull() | |
&& (current == null || current.isJsonNull())) { | |
// add new field | |
resolveChanges(old, changes, fieldPath, false); | |
} else if (current != null && !current.isJsonNull() | |
&& (old == null || old.isJsonNull())) { | |
// remove field | |
resolveChanges(current, changes, fieldPath, true); | |
} else { | |
// field aligned | |
if (current.isJsonPrimitive()) { | |
if (!Objects.equals(old.getAsString(), current.getAsString())) { | |
changes.add(new EntryValueChange(path, old.getAsString(), current.getAsString())); | |
} | |
} else if (current.isJsonArray()) { | |
JsonArray oldArray = old.getAsJsonArray(); | |
JsonArray currentArray = current.getAsJsonArray(); | |
for (int i=0; i<oldArray.size(); i++) { | |
fieldPath.push(Integer.toString(i)); | |
if (i < currentArray.size()) { | |
resolveChanges(oldArray.get(i), currentArray.get(i), changes, fieldPath); | |
} else { | |
resolveChanges(oldArray.get(i), changes, fieldPath, false); | |
} | |
fieldPath.pop(); | |
} | |
for (int i=oldArray.size(); i<currentArray.size(); i++) { | |
fieldPath.push(Integer.toString(i)); | |
resolveChanges(currentArray.get(i), changes, fieldPath, true); | |
fieldPath.pop(); | |
} | |
} else if (current.isJsonObject()) { | |
JsonObject oldObj = old.getAsJsonObject(); | |
JsonObject currentObj = current.getAsJsonObject(); | |
oldObj.entrySet().forEach(oldEntry -> { | |
String key = oldEntry.getKey(); | |
fieldPath.push(key); | |
if (currentObj.has(key)) { | |
resolveChanges(oldEntry.getValue(), currentObj.get(key), changes, fieldPath); | |
} else { | |
resolveChanges(oldEntry.getValue(), changes, fieldPath, false); | |
} | |
fieldPath.pop(); | |
}); | |
currentObj.entrySet().forEach(currentEntry -> { | |
String key = currentEntry.getKey(); | |
fieldPath.push(key); | |
if (!currentObj.has(key)) { | |
resolveChanges(currentEntry.getValue(), changes, fieldPath, true); | |
} | |
fieldPath.pop(); | |
}); | |
} | |
} | |
} | |
private void resolveChanges(JsonElement json, List<EntryChange> changes, Stack<String> fieldPath, boolean addOrRemove) { | |
String path = fieldPath.stream().collect(Collectors.joining(".")); | |
if (json.isJsonPrimitive()) { | |
EntryAddOrRemove entryChange = addOrRemove | |
? new EntryAdded(path, json.getAsString()) | |
: new EntryRemoved(path, json.getAsString()); | |
changes.add(entryChange); | |
} else if (json.isJsonArray()) { | |
JsonArray array = json.getAsJsonArray(); | |
for (int i=0; i<array.size(); i++) { | |
fieldPath.push(Integer.toString(i)); | |
resolveChanges(array.get(i), changes, fieldPath, addOrRemove); | |
fieldPath.pop(); | |
} | |
} else if (json.isJsonObject()) { | |
JsonObject obj = json.getAsJsonObject(); | |
obj.entrySet().forEach(entry -> { | |
fieldPath.push(entry.getKey()); | |
resolveChanges(entry.getValue(), changes, fieldPath, addOrRemove); | |
fieldPath.pop(); | |
}); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment