Diff Configuration

JaVers’ diff algorithm has a pluggable construction. It consists of the core comparators suite and optionally, custom comparators.

You can fine-tune how the whole algorithm works by registering custom comparators for certain types (custom comparators overrides core comparators).

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 comparing strategy for certain types.

Custom Property Comparators come to the rescue. 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 comparator to Custom Type, which means: I don’t care what it is, all I know is that it should be compared using this custom comparator.

All you have to do is implement the CustomPropertyComparator interface:

public interface CustomPropertyComparator<T, C extends PropertyChange> {
    /**
     * Called by JaVers to calculate property-to-property diff.
     */
    Optional<C> compare(T left, T right, GlobalId affectedId, Property property);

    /**
     * Called by JaVers to calculate collection-to-collection diff.
     */
    boolean equals(T a, T b);
}

and register your custom comparator instance in JaversBuilder:

JaversBuilder.javers().registerCustomComparator(new MyClassComparator(), MyClass.class).build()

See the full example of CustomPropertyComparator in our examples chapter.

Custom comparators for Values

The natural way of providing 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 custom comparator.

A CustomValueComparator implements the single boolean equals(a, b) method:

@FunctionalInterface
public interface CustomValueComparator<T> {
    boolean equals(T left, T right);
}

and it can be super-easily registered in JaversBuilder, for example:

Javers javers = JaversBuilder.javers()
        .registerValue(BigDecimal.class, (a, b) -> a.intValue() == b.intValue()).build();

Given equals() method is used by JaVers to calculate both collection-to-collection diff and property-to-property diff. Note that for Value types, property-to-property diff is always a ValueChange.

Unlike CustomPropertyComparator which offers great flexibility, CustomValueComparator is just a way to provide other equals() implementation for given Value class.