diff --git a/src/main/java/com/github/tonivade/vavr/effect/IO.java b/src/main/java/com/github/tonivade/vavr/effect/IO.java index aa179af..b528fbf 100644 --- a/src/main/java/com/github/tonivade/vavr/effect/IO.java +++ b/src/main/java/com/github/tonivade/vavr/effect/IO.java @@ -662,24 +662,9 @@ } } -interface IOConnection { +sealed interface IOConnection { - IOConnection UNCANCELLABLE = new IOConnection() { - @Override - public boolean isCancellable() { return false; } - - @Override - public void setCancelToken(IO cancel) { } - - @Override - public void cancelNow() { } - - @Override - public void cancel() { } - - @Override - public StateIO updateState(UnaryOperator update) { return StateIO.INITIAL; } - }; + IOConnection UNCANCELLABLE = new Uncancellable(); boolean isCancellable(); @@ -692,82 +677,122 @@ StateIO updateState(UnaryOperator update); static IOConnection cancellable() { - return new IOConnection() { - - private IO cancelToken; - private final AtomicReference state = new AtomicReference<>(StateIO.INITIAL); - - @Override - public boolean isCancellable() { return true; } - - @Override - public void setCancelToken(IO cancel) { this.cancelToken = Objects.requireNonNull(cancel); } - - @Override - public void cancelNow() { cancelToken.runAsync(); } - - @Override - public void cancel() { - if (state.getAndUpdate(StateIO::cancellingNow).isCancelable()) { - cancelNow(); - - state.set(StateIO.CANCELLED); - } + return new Cancellable(); + } + + static final class Uncancellable implements IOConnection { + + private Uncancellable() { } + + @Override + public boolean isCancellable() { + return false; + } + + @Override + public void setCancelToken(IO cancel) { + // uncancellable + } + + @Override + public void cancelNow() { + // uncancellable + } + + @Override + public void cancel() { + // uncancellable + } + + @Override + public StateIO updateState(UnaryOperator update) { + return StateIO.INITIAL; + } + } + + static final class Cancellable implements IOConnection { + + private IO cancelToken; + private final AtomicReference state = new AtomicReference<>(StateIO.INITIAL); + + private Cancellable() { } + + @Override + public boolean isCancellable() { + return true; + } + + @Override + public void setCancelToken(IO cancel) { + this.cancelToken = Objects.requireNonNull(cancel); + } + + @Override + public void cancelNow() { + cancelToken.runAsync(); + } + + @Override + public void cancel() { + if (state.getAndUpdate(StateIO::cancellingNow).isCancelable()) { + cancelNow(); + + state.set(StateIO.CANCELLED); } - - @Override - public StateIO updateState(UnaryOperator update) { - return state.updateAndGet(update::apply); - } - }; + } + + @Override + public StateIO updateState(UnaryOperator update) { + return state.updateAndGet(update::apply); + } } } final class StateIO { - public static final StateIO INITIAL = new StateIO(false, false, false); - public static final StateIO CANCELLED = new StateIO(true, false, false); + static final StateIO INITIAL = new StateIO(false, false, false); + static final StateIO CANCELLED = new StateIO(true, false, false); - private final boolean isCancelled; + private final boolean cancelled; private final boolean cancellingNow; private final boolean startingNow; - public StateIO(boolean isCancelled, boolean cancellingNow, boolean startingNow) { - this.isCancelled = isCancelled; + StateIO(boolean cancelled, boolean cancellingNow, boolean startingNow) { + this.cancelled = cancelled; this.cancellingNow = cancellingNow; this.startingNow = startingNow; } - public boolean isCancelled() { - return isCancelled; + boolean isCancelled() { + return cancelled; } - public boolean isCancellingNow() { + boolean isCancellingNow() { return cancellingNow; } - public boolean isStartingNow() { + boolean isStartingNow() { return startingNow; } - public StateIO cancellingNow() { - return new StateIO(isCancelled, true, startingNow); + StateIO cancellingNow() { + return new StateIO(cancelled, true, startingNow); } - public StateIO startingNow() { - return new StateIO(isCancelled, cancellingNow, true); + StateIO startingNow() { + return new StateIO(cancelled, cancellingNow, true); } - public StateIO notStartingNow() { - return new StateIO(isCancelled, cancellingNow, false); + StateIO notStartingNow() { + return new StateIO(cancelled, cancellingNow, false); } - public boolean isCancelable() { - return !isCancelled && !cancellingNow && !startingNow; + boolean isCancelable() { + return !cancelled && !cancellingNow && !startingNow; } - public boolean isRunnable() { - return !isCancelled && !cancellingNow; + boolean isRunnable() { + return !cancelled && !cancellingNow; } } @@ -775,11 +800,11 @@ private StackItem top = new StackItem<>(); - public void push() { + void push() { top.push(); } - public void pop() { + void pop() { if (top.count() > 0) { top.pop(); } else { @@ -787,7 +812,7 @@ } } - public void add(PartialFunction> mapError) { + void add(PartialFunction> mapError) { if (top.count() > 0) { top.pop(); top = new StackItem<>(top); @@ -795,7 +820,7 @@ top.add(mapError); } - public Option> tryHandle(Throwable error) { + Option> tryHandle(Throwable error) { while (top != null) { top.reset(); Option> result = top.tryHandle(error); @@ -811,7 +836,7 @@ // XXX: https://www.baeldung.com/java-sneaky-throws @SuppressWarnings("unchecked") - public R sneakyThrow(Throwable t) throws X { + R sneakyThrow(Throwable t) throws X { throw (X) t; } } @@ -823,39 +848,39 @@ private final StackItem prev; - public StackItem() { + StackItem() { this(null); } - public StackItem(StackItem prev) { + StackItem(StackItem prev) { this.prev = prev; } - public StackItem prev() { + StackItem prev() { return prev; } - public int count() { + int count() { return count; } - public void push() { + void push() { count++; } - public void pop() { + void pop() { count--; } - public void reset() { + void reset() { count = 0; } - public void add(PartialFunction> mapError) { + void add(PartialFunction> mapError) { recover.addFirst(mapError); } - public Option> tryHandle(Throwable error) { + Option> tryHandle(Throwable error) { while (!recover.isEmpty()) { var mapError = recover.removeFirst(); if (mapError.isDefinedAt(error)) {