Skip to content

Commit 028c4da

Browse files
committed
feat: add interaction results
1 parent 6e55d9e commit 028c4da

3 files changed

Lines changed: 123 additions & 17 deletions

File tree

state-core/src/main/java/org/incendo/state/StateInteraction.java

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525

2626
import java.util.Objects;
2727
import java.util.function.Consumer;
28-
import java.util.function.Function;
29-
import java.util.function.UnaryOperator;
3028
import org.apiguardian.api.API;
3129
import org.checkerframework.checker.nullness.qual.NonNull;
3230
import org.checkerframework.common.returnsreceiver.qual.This;
@@ -49,9 +47,11 @@ public interface StateInteraction<U extends State<U>, V extends Stateful<U, V>>
4947
/**
5048
* Executes the interaction.
5149
*
50+
* <p>Any checked exceptions will be wrapped in {@link RuntimeException}.</p>
51+
*
5252
* @return the result of the interaction
5353
*/
54-
@NonNull V execute();
54+
@NonNull InteractionResult<U, V> execute();
5555

5656
final class Builder<U extends State<U>, V extends Stateful<U, V>> implements StateInteraction<U, V> {
5757

@@ -60,14 +60,14 @@ final class Builder<U extends State<U>, V extends Stateful<U, V>> implements Sta
6060
private States<U> incomingStates;
6161
private States<U> outgoingStates;
6262
private States<U> shortCircuitStates;
63-
private Function<V, V> interaction;
63+
private Interaction<U, V> interaction;
6464

6565
private Builder(final @NonNull V instance) {
6666
this.instance = Objects.requireNonNull(instance, "instance");
6767
this.incomingStates = States.of(instance.state());
6868
this.outgoingStates = instance.allowedTransitions();
6969
this.shortCircuitStates = States.of();
70-
this.interaction = UnaryOperator.identity();
70+
this.interaction = Interaction.identity();
7171
}
7272

7373
/**
@@ -112,7 +112,7 @@ private Builder(final @NonNull V instance) {
112112
* @param interaction interaction
113113
* @return {@code this}
114114
*/
115-
public @This @NonNull Builder<U, V> interaction(final @NonNull Function<V, V> interaction) {
115+
public @This @NonNull Builder<U, V> interaction(final @NonNull Interaction<U, V> interaction) {
116116
this.interaction = Objects.requireNonNull(interaction, "interaction");
117117
return this;
118118
}
@@ -151,8 +151,98 @@ private Builder(final @NonNull V instance) {
151151
* @return the result of the interaction
152152
*/
153153
@Override
154-
public @NonNull V execute() {
154+
public @NonNull InteractionResult<U, V> execute() {
155155
return this.build().execute();
156156
}
157157
}
158+
159+
@FunctionalInterface
160+
interface Interaction<U extends State<U>, V extends Stateful<U, V>> {
161+
162+
/**
163+
* Returns an interaction that immediately returns the incoming stateful.
164+
*
165+
* @param <U> state type
166+
* @param <V> stateful type
167+
* @return identity interaction
168+
*/
169+
static <U extends State<U>, V extends Stateful<U, V>> @NonNull Interaction<U, V> identity() {
170+
return interaction -> interaction;
171+
}
172+
173+
/**
174+
* Performs the interaction.
175+
*
176+
* @param stateful stateful instance to perform interaction on
177+
* @return result of the interaction
178+
*/
179+
@NonNull V interact(@NonNull V stateful) throws Throwable;
180+
}
181+
182+
sealed interface InteractionResult<U extends State<U>, V extends Stateful<U, V>> {
183+
184+
/**
185+
* Returns the stateful instance involved in the interaction.
186+
*
187+
* @return stateful instance
188+
*/
189+
@NonNull V instance();
190+
191+
/**
192+
* Unwraps the result.
193+
*
194+
* @return the result of the interaction
195+
*/
196+
@NonNull V unwrap();
197+
198+
record ShortCircuited<U extends State<U>, V extends Stateful<U, V>>(
199+
@NonNull V instance
200+
) implements InteractionResult<U, V> {
201+
202+
@Override
203+
public @NonNull V unwrap() {
204+
return this.instance;
205+
}
206+
}
207+
208+
record Succeeded<U extends State<U>, V extends Stateful<U, V>>(
209+
@NonNull V instance,
210+
@NonNull V result
211+
) implements InteractionResult<U, V> {
212+
213+
@Override
214+
public @NonNull V unwrap() {
215+
return this.result;
216+
}
217+
}
218+
219+
sealed interface Failed<U extends State<U>, V extends Stateful<U, V>> extends InteractionResult<U, V> {
220+
221+
/**
222+
* Returns the exception produced by the interaction.
223+
*
224+
* @return interaction exception
225+
*/
226+
@NonNull UnexpectedStateException exception();
227+
228+
@Override
229+
default @NonNull V unwrap() {
230+
throw this.exception();
231+
}
232+
233+
record IllegalIncomingState<U extends State<U>, V extends Stateful<U, V>>(
234+
@NonNull V instance,
235+
@NonNull UnexpectedStateException exception
236+
) implements Failed<U, V> {
237+
}
238+
239+
record IllegalOutgoingState<U extends State<U>, V extends Stateful<U, V>>(
240+
@NonNull V instance,
241+
@NonNull V result,
242+
@NonNull UnexpectedStateException exception
243+
) implements Failed<U, V> {
244+
245+
}
246+
}
247+
}
158248
}

state-core/src/main/java/org/incendo/state/StateInteractionImpl.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
package org.incendo.state;
2525

2626
import java.util.concurrent.locks.Lock;
27-
import java.util.function.Function;
2827
import org.apiguardian.api.API;
2928
import org.checkerframework.checker.nullness.qual.NonNull;
3029

@@ -34,11 +33,11 @@ record StateInteractionImpl<U extends State<U>, V extends Stateful<U, V>>(
3433
@NonNull States<U> incomingStates,
3534
@NonNull States<U> outgoingStates,
3635
@NonNull States<U> shortcircuitStates,
37-
@NonNull Function<V, V> interaction
36+
@NonNull Interaction<U, V> interaction
3837
) implements StateInteraction<U, V> {
3938

4039
@Override
41-
public @NonNull V execute() {
40+
public @NonNull InteractionResult<U, V> execute() {
4241
final Lock lock;
4342
if (this.instance instanceof AbstractLockableStateful<?, ?> lockableStateful) {
4443
lock = lockableStateful.lock().writeLock();
@@ -54,21 +53,36 @@ record StateInteractionImpl<U extends State<U>, V extends Stateful<U, V>>(
5453
final U currentState = this.instance.state();
5554

5655
if (this.shortcircuitStates.contains(currentState)) {
57-
return this.instance;
56+
return new InteractionResult.ShortCircuited<>(this.instance);
5857
}
5958

6059
if (!this.incomingStates.contains(currentState)) {
61-
throw new UnexpectedStateException(this.incomingStates, currentState, this.instance);
60+
return new InteractionResult.Failed.IllegalIncomingState<>(
61+
this.instance,
62+
new UnexpectedStateException(this.incomingStates, currentState, this.instance)
63+
);
6264
}
6365

64-
final V result = this.interaction.apply(this.instance);
66+
final V result;
67+
68+
try {
69+
result = this.interaction.interact(this.instance);
70+
} catch (final RuntimeException e) {
71+
throw e;
72+
} catch (final Throwable throwable) {
73+
throw new RuntimeException(throwable);
74+
}
6575

6676
final U newState = result.state();
6777
if (!this.outgoingStates.contains(newState)) {
68-
throw new UnexpectedStateException(this.outgoingStates, newState, result);
78+
return new InteractionResult.Failed.IllegalOutgoingState<>(
79+
this.instance,
80+
result,
81+
new UnexpectedStateException(this.outgoingStates, newState, result)
82+
);
6983
}
7084

71-
return result;
85+
return new InteractionResult.Succeeded<>(this.instance, result);
7286
} finally {
7387
if (lock != null) {
7488
lock.unlock();

state-core/src/test/java/org/incendo/state/IntegrationTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,17 @@ void test() {
6868
.outgoingStates(States.of(TestState.INTERMEDIARY_FOO))
6969
.shortCircuitStates(States.of(TestState.INTERMEDIARY_FOO))
7070
.interaction(instance -> instance.foo().transitionTo(TestState.INTERMEDIARY_FOO))
71-
.execute();
71+
.execute()
72+
.unwrap();
7273
}
7374

7475
private @NonNull TestObject end(final @NonNull TestObject object) {
7576
return object.interact()
7677
.outgoingStates(States.of(TestState.END_STATE))
7778
.shortCircuitStates(States.of(TestState.END_STATE))
7879
.interaction(instance -> instance.transitionTo(TestState.END_STATE))
79-
.execute();
80+
.execute()
81+
.unwrap();
8082
}
8183

8284
enum TestState implements State<TestState> {

0 commit comments

Comments
 (0)