Skip to content

Latest commit

 

History

History
148 lines (103 loc) · 4.9 KB

README.md

File metadata and controls

148 lines (103 loc) · 4.9 KB

Riff - Rich difference generator

Build Coverage Branches

About

Riff is a rich difference generator between two Java collections of the same type. It works similarly to the Unix diff command, and its output is a map of qualified changes aka. riff ("rich diff").

The generation process identifies changes between the two collections, left and right as such:

left + riff = right

Canonically left holds the "before" state and right holds the "after" state.

Once riff is generated, it will contain the following qualified changes:

  • ADDED: Elements in right but not in left
  • REMOVED: Elements in left but not in right
  • MODIFIED: Elements both in left and right with content changes (see Deep equality check)
  • UNMODIFIED: Elements both in left and right without content changes.

Riff generator strategies

Generator classes extend the AbstractRiffGenerator base class, which exposes common protected methods for setting states. Implementing subclasses should override and expose only those methods they need to work with.

Generators follow the builder pattern, calling a chain of methods to set the required states, terminating with generate().

Left-right comparing riff generator

ComparingRiffGenerator implements a standard generator that accepts left and right, then compares them to generate the riff.

Example usage

// Generate the riff
RiffData<MyClass> riff = new ComparingRiffGenerator<MyClass>()
        .withLeft(left)
        .withRight(right)
        .generate();

// Get the changes
Map<ChangeType, Set<MyClass>> changes = riff.getChanges();

Change specifying riff generator

ChangeSpecifyingRiffGenerator implements a generator which accepts left and an explicitly stated list of changes. This class infers right by applying the changes to left.

Example usage

// Generate the riff
RiffData<MyClass> riff = new ChangeSpecifyingRiffGenerator<MyClass>()
        .withLeft(left)
        .added(alice)
        .added(bob)
        .removed(charlie)
        .modified(diane)
        .generate();

// Get the changes
Map<ChangeType, Set<MyClass>> changes = riff.getChanges();

// Get the inferred right collection
Set<MyClass> right = riff.getRight();

Custom riff generator

You can implement your own riff generator by extending AbstractRiffGenerator.

Example usage

import io.demjened.riff.generators.AbstractRiffGenerator;
import io.demjened.riff.model.RiffData;

import java.util.Collection;

class MyRiffGenerator<T> extends AbstractRiffGenerator<T> {

    // Override those methods you want to use in a builder chain, and cast to your subclass
    @Override
    protected AbstractRiffGenerator<T> withLeft(Collection<T> left) {
        /* Custom logic */

        return (MyRiffGenerator) super.withLeft(left);
    }

    @Override
    public RiffData<T> generate() {
        // Implement generator logic that returns a populated RiffData
    }

}

Configuration

The base generator class accepts an optional RiffConfig object, which can configure the behavior

Deep equality check

Riff generators use the standard identity equality check (equals()) to check whether an item is in left and/or right. The process cannot determine whether an item both in left and right is changing in content, that is, some non-identity properties are being modified. By default such items will be marked as UNMODIFIED in the riff.

In order to assess content changes, the client needs to implement a deep equality check bifunction of type BiFunction<T, T, Boolean> and pass it to the config object with withDeepEqualityCheck().

Example usage

class Person {
    
    private Long id;
    private String name;
    private Integer age;
    private Food favoriteFood; // Does not count as "modified" even if it changes
    
    @Override
    public boolean equals() {
        // Assuming equals() and hashCode() are implemented on the id property
    } 

    // Deep equality on id, name and age
    public boolean deepEquals(Person that) {
        return Objects.equals(this.id, that.id)
                && Objects.equals(this.name, that.name)
                && Objects.equals(this.age, that.age);
    }
    
}

Set<Person> left = Set.of(new Person(1, "Alice", 25, Food.SUSHI), ...);
Set<Person> right = Set.of(new Person(1, "Alice", 38, Food.SUSHI), ...);

// Riff generation
RiffData<Person> riff = new ComparingRiffGenerator()
        .withConfig(new RiffConfig<Person>()
                .withDeepEqualityCheck(Person::deepEquals))
        .withLeft(left)
        .withRight(right)
        .generate();

Set<Person> modified = riff.getChanges().get(ChangeType.MODIFIED); // Will contain "Alice"

See the unit tests for more detailed examples.

Item cloning

TBD