Javers’ diff algorithm has a pluggable construction. Each Java type is mapped to exact one Javers type. Each Javers type is mapped to exact one comparator.
In most cases, you will rely on Javers’ core comparators. Optionally, you can register Custom comparators for Value types and Custom Types.
For comparing Lists, JaVers has three core comparators: Simple (default), Levenshtein distance, and Set. Pick one.
List comparing algorithms
Generally, we recommend using Levenshtein, because it’s the smartest one. But use it with caution, it could be slow for long lists, say more then 300 elements.
The main advantage of Simple algorithm is speed, it has linear computation complexity. The main disadvantage is the verbose output.
Choose the Set algorithm if you don’t care about the items ordering.
JaVers will convert all Lists to Sets before comparision.
This algorithm produces the most concise output (only ValueAdded
and ValueRemoved
).
You can switch to Levenshtein or Set in JaversBuilder:
Javers javers = JaversBuilder.javers()
.withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE)
.build();
or
Javers javers = JaversBuilder.javers()
.withListCompareAlgorithm(ListCompareAlgorithm.AS_SET)
.build();
Simple vs Levenshtein algorithm
Simple algorithm generates changes for shifted elements (in case when elements are inserted or removed in the middle of a list). On the contrary, Levenshtein algorithm calculates short and clear change list even in case when elements are shifted. It doesn’t care about index changes for shifted elements.
For example, when we remove one element from a list:
javers.compare(['a','b','c','d','e'],
['a','c','d','e'])
the change list will be different, depending on chosen algorithm:
Output from Simple algorithm | Output from Levenshtein algorithm |
---|---|
(1). 'b'>>'c' (2). 'c'>>'d' (3). 'd'>>'e' (4). removed:'e' |
(1). removed: 'b' |
But when both lists have the same size:
javers.compare(['a','b','c','d'],
['a','g','e','i'])
the change list will the same:
Simple algorithm | Levenshtein algorithm |
---|---|
(1). 'b'>>'g' (2). 'c'>>'e' (3). 'd'>>'i' |
(1). 'b'>>'g' (2). 'c'>>'e' (3). 'd'>>'i' |
More about Levenshtein distance
The idea is based on the Levenshtein edit distance algorithm, usually used for comparing Strings. That is answering the question what changes should be done to go from one String to another?
Since a list of characters (i.e. String) is equal to a list of objects up to isomorphism we can use the same algorithm for finding the Levenshtein edit distance for list of objects.
The algorithm is based on computing the shortest path in a DAG. It takes both O(nm)
space
and time. Further work should improve it to take O(n)
space and O(nm)
time (n and m being
the length of both compared lists).
Custom Comparators
There are cases where Javers’ diff algorithm isn’t appropriate, and you need to implement your own comparator for certain types.
There are two types of Custom comparators: for Value types,
you can register
a CustomValueComparator
and for Custom types, you register
a CustomPropertyComparator
.
Custom Value Comparators
The natural way of providing a comparing strategy for Value
classes is overriding the standard Object.equals(Object)
method.
If you don’t control the source code of a given Value class,
you can still change its comparing strategy by registering
a CustomValueComparator
.
It has two methods:
public interface CustomValueComparator<T> {
boolean equals(T a, T b);
String toString(T value);
}
and it can be easily registered in JaversBuilder, for example:
JaversBuilder.javers()
.registerValue(BigDecimal.class, new BigDecimalComparatorWithFixedEquals())
.build();
or with lambdas:
Javers javers = JaversBuilder.javers()
.registerValue(BigDecimal.class, (a, b) -> a.compareTo(b) == 0,
a -> a.stripTrailingZeros().toString())
.build();
Then, given equals()
function is used instead of Object.equals()
to compare Values
and given toString()
function is used instead of Object.hashCode()
when Values are compared in hashing contexts.
See the full example of Custom Value comparator in our examples chapter.
Custom Property Comparators
Unlike Custom Value comparators which are just a way to provide external equals()
implementation for Value classes,
Custom Property comparators offer great flexibility.
You can register them for any type (class or interface) to bypass the Javers’ type system and diff algorithm.
Javers maps a class with a Custom Property comparator to Custom Type,
which means:
I don’t care what it is, all I know is that it should be compared using this comparator.
Yet, Custom Types are not easy to manage, use it as a last resort,
only for corner cases like comparing custom Collection types.
To register a Custom Type, all you have to do is implement the
CustomPropertyComparator
interface:
public interface CustomPropertyComparator<T, C extends PropertyChange>
extends CustomValueComparator<T>
{
Optional<C> compare(T left, T right, PropertyChangeMetadata metadata, Property property);
}
and register it in JaversBuilder, for example:
Javers javers = JaversBuilder.javers()
.registerCustomType(MyClass.class, new MyClassComparator())
.build();
See the full example of Custom Property comparator.