Explorer.java overhaul
public class Explorer<T> {
private final Supplier<T> from;
private final Supplier<T> to;
private final Supplier<T> absent;
private final BiFunction<T, T, Boolean> bounder;
private final Function<T, T> incrementer;
private Explorer(Builder<T> builder) {
this.from = builder.from;
this.to = builder.to;
this.absent = builder.absent;
this.bounder = builder.bounder;
this.incrementer = builder.incrementer;
}
public T from() { return from.get(); }
public T to() { return to.get(); }
public T absent() { return absent.get(); }
public boolean inbound(T index, T bound) { return bounder.apply(index, bound); }
public T increment(T index) { return incrementer.apply(index); }
public static <T> Builder<T> builder() {
return new Builder<>();
}
public static class Builder<T> {
private Supplier<T> from;
private Supplier<T> to;
private Supplier<T> absent;
private BiFunction<T, T, Boolean> bounder;
private Function<T, T> incrementer;
public Builder<T> from(Supplier<T> from) {
this.from = from;
return this;
}
public Builder<T> to(Supplier<T> to) {
this.to = to;
return this;
}
public Builder<T> absent(Supplier<T> absent) {
this.absent = absent;
return this;
}
public Builder<T> bounder(BiFunction<T, T, Boolean> bounder) {
this.bounder = bounder;
return this;
}
public Builder<T> incrementer(Function<T, T> incrementer) {
this.incrementer = incrementer;
return this;
}
public Explorer<T> build() {
validate();
return new Explorer<>(this);
}
private void validate() {
if (from == null || to == null || bounder == null || incrementer == null || absent == null) {
throw new IllegalStateException("All builder properties must be set");
}
}
}
}
This enhanced version is based on the introduction of a Builder Pattern (with proper validation). Now the fields are final to ensure immutability and redundant methods are now refactored. I've used also generics to ensure type safety.
NextIndex.java overhaul
public class NextIndex {
public static <T> Lookup<T> of(T element) {
return new Lookup<>(element);
}
public static class Lookup<T> {
private final T element;
private final List<T> source;
private final int fromIndex;
private final Direction direction;
private Lookup(T element) {
this(element, new ArrayList<>(), 0, Direction.FORWARD);
}
private Lookup(T element, List<T> source, int fromIndex, Direction direction) {
this.element = element;
this.source = new ArrayList<>(source);
this.fromIndex = fromIndex;
this.direction = direction;
}
public Lookup<T> into(Collection<T> source) {
return new Lookup<>(element, new ArrayList<>(source), fromIndex, direction);
}
public Lookup<T> fromIndex(int index) {
return new Lookup<>(element, source, index, direction);
}
public Lookup<T> forward() {
return new Lookup<>(element, source, fromIndex, Direction.FORWARD);
}
public Lookup<T> reverse() {
return new Lookup<>(element, source, fromIndex, Direction.REVERSE);
}
public int absolute() {
Explorer<Integer> explorer = direction.createExplorer(fromIndex, source.size());
return findIndex(explorer, false);
}
public int relative() {
Explorer<Integer> explorer = direction.createExplorer(fromIndex, source.size());
return findIndex(explorer, true);
}
private int findIndex(Explorer<Integer> explorer, boolean relative) {
if (!isValidIndex(fromIndex)) {
return explorer.absent();
}
int index = explorer.absent();
for (int i = explorer.from(); explorer.inbound(i, explorer.to()); i = explorer.increment(i)) {
if (Objects.equals(element, source.get(i))) {
index = relative ? i - fromIndex : i;
break;
}
}
return index;
}
private boolean isValidIndex(int index) {
return index >= 0 && index <= source.size();
}
}
private enum Direction {
FORWARD {
@Override
Explorer<Integer> createExplorer(int fromIndex, int size) {
return Explorer.<Integer>builder()
.from(() -> fromIndex)
.to(() -> size)
.bounder((index, bound) -> index < bound)
.incrementer(index -> index + 1)
.absent(() -> -1)
.build();
}
},
REVERSE {
@Override
Explorer<Integer> createExplorer(int fromIndex, int size) {
return Explorer.<Integer>builder()
.from(() -> fromIndex > 0 ? fromIndex : size - 1)
.to(() -> 0)
.bounder((index, bound) -> index >= bound)
.incrementer(index -> index - 1)
.absent(() -> -1)
.build();
}
};
abstract Explorer<Integer> createExplorer(int fromIndex, int size);
}
}
NextIndex class now owns a Lookup instance with no state mutations and it also owns a Direction enum to handle forward/reverse operations. I've also implemented an improved encapsulation with private fields and methods, refactoring also the index finding logic. Now thread's safety of the code is improved by immutable features.