Forker

Overview

Forker<F> is the strategy object that knows how to build a forked instance of a specific entity type F. The interface defines three methods, one per ForkMode:

public interface Forker<F> {
    default F toCopy()     { throw new UnsupportedOperationException(...); }
    default F toNext()     { throw new UnsupportedOperationException(...); }
    default F toPrevious() { throw new UnsupportedOperationException(...); }
}

Every method has a default implementation that throws UnsupportedOperationExceptionimplementations override only the modes they support. A common pattern is to support ToNext (period-boundary carry-forward) and ToCopy, but not ToPrevious.

Implementing a Forker

A Forker is typically a small class that holds a reference to the original and produces new instances:

public class NinjaForker implements Forker<Ninja> {

    private final Ninja original;

    public NinjaForker(Ninja original) { this.original = original; }

    @Override
    public Ninja toNext() {
        var n = new Ninja();
        n.setUuid(UUID.randomUUID());
        n.setSeason(original.getSeason() + 1);
        n.setForkedFrom(original.toForkSources(ForkMode.ToNext));
        n.setDojo(ForkRefAware.ofNullable(original.getDojo(), ForkMode.ToNext));
        n.setRank(original.getRank());
        return n;
    }
}

Three things to notice:

  1. Lineage is recorded via original.toForkSources(ForkMode.ToNext) — you don’t construct ForkSources by hand.

  2. References to other forkable entities (here dojo) go through ForkRefAware.ofNullable(…​), which produces a minimal shell with just the UUID and lineage. This avoids deep-copying entire object graphs, and lets the consumer decide later whether to materialise the full child.

  3. Plain data fields (here rank) are copied directly.

The dispatch from Forkable.fork(ForkMode) to the right Forker method is handled by the library:

@Override
public Forker<Ninja> fork() {
    return new NinjaForker(this);
}

// caller:
var next = original.fork(ForkMode.ToNext);