diff --git a/gradle/build-logic/src/main/kotlin/state.base-conventions.gradle.kts b/gradle/build-logic/src/main/kotlin/state.base-conventions.gradle.kts index 09865f8..c3cc58d 100644 --- a/gradle/build-logic/src/main/kotlin/state.base-conventions.gradle.kts +++ b/gradle/build-logic/src/main/kotlin/state.base-conventions.gradle.kts @@ -7,8 +7,9 @@ indra { checkstyle().set(libs.versions.checkstyle) javaVersions { + minimumToolchain(17) target(17) - testWith(17) + testWith().set(setOf(17)) } } diff --git a/state-core/build.gradle.kts b/state-core/build.gradle.kts index b69fe8c..bc21764 100644 --- a/state-core/build.gradle.kts +++ b/state-core/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("state.base-conventions") - // id("state.publishing-conventions") + id("state.publishing-conventions") } dependencies { diff --git a/state-core/src/main/java/org/incendo/state/AbstractStateful.java b/state-core/src/main/java/org/incendo/state/AbstractStateful.java index de318df..a138613 100644 --- a/state-core/src/main/java/org/incendo/state/AbstractStateful.java +++ b/state-core/src/main/java/org/incendo/state/AbstractStateful.java @@ -26,48 +26,52 @@ import java.util.Objects; import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.returnsreceiver.qual.This; /** * A thread-safe implementation of {@link MutableStateful}. * - * @param state type + * @param state type + * @param self-referencing type * @since 1.0.0 */ @API(status = API.Status.STABLE, since = "1.0.0") -public abstract class AbstractStateful> implements MutableStateful { +public abstract class AbstractStateful, V extends AbstractStateful> implements MutableStateful { - private S state; + private U state; /** * Creates a new instance. * * @param initialState initial state */ - protected AbstractStateful(final @NonNull S initialState) { + protected AbstractStateful(final @NonNull U initialState) { this.state = Objects.requireNonNull(initialState, "initialState"); } @Override - public final synchronized @NonNull S state() { + public final synchronized @NonNull U state() { return this.state; } @Override - public final synchronized void transitionTo(final @NonNull S state) throws IllegalStateTransitionException { + @SuppressWarnings("unchecked") + public final synchronized @This @NonNull V transitionTo(final @NonNull U state) throws IllegalStateTransitionException { Objects.requireNonNull(state, "state"); if (!this.canTransitionTo(state)) { throw new IllegalStateTransitionException(this.state, state, this); } this.state = state; + return (V) this; } @Override - public final synchronized void transition(final @NonNull S currentState, final @NonNull S newState) + public final synchronized @This @NonNull V transition(final @NonNull U currentState, final @NonNull U newState) throws UnexpectedStateException, IllegalStateTransitionException { Objects.requireNonNull(currentState, "currentState"); - if (!this.state.equals(newState)) { - throw new UnexpectedStateException(currentState, newState, this); + if (!this.state.equals(currentState)) { + throw new UnexpectedStateException(States.of(currentState), newState, this); } - this.transitionTo(newState); + return this.transitionTo(newState); } } diff --git a/state-core/src/main/java/org/incendo/state/EmptyStates.java b/state-core/src/main/java/org/incendo/state/EmptyStates.java index f62de1c..12e7e9c 100644 --- a/state-core/src/main/java/org/incendo/state/EmptyStates.java +++ b/state-core/src/main/java/org/incendo/state/EmptyStates.java @@ -40,4 +40,9 @@ public boolean contains(final @NonNull S state) { public @NonNull States withState(final @NonNull S state) { return States.of(state); } + + @Override + public String toString() { + return "()"; + } } diff --git a/state-core/src/main/java/org/incendo/state/IllegalStateTransitionException.java b/state-core/src/main/java/org/incendo/state/IllegalStateTransitionException.java index 7a2769f..84b661a 100644 --- a/state-core/src/main/java/org/incendo/state/IllegalStateTransitionException.java +++ b/state-core/src/main/java/org/incendo/state/IllegalStateTransitionException.java @@ -36,7 +36,7 @@ public class IllegalStateTransitionException extends IllegalArgumentException { private final State from; private final State to; - private final MutableStateful stateful; + private final MutableStateful stateful; /** * Creates a new instance. @@ -48,7 +48,7 @@ public class IllegalStateTransitionException extends IllegalArgumentException { public IllegalStateTransitionException( final @NonNull State from, final @NonNull State to, - final @NonNull MutableStateful stateful + final @NonNull MutableStateful stateful ) { super(String.format("Cannot transition from state '%s' to state '%s'", from, to)); this.from = from; @@ -79,7 +79,7 @@ public IllegalStateTransitionException( * * @return stateful instance */ - public @NonNull MutableStateful stateful() { + public @NonNull MutableStateful stateful() { return this.stateful; } } diff --git a/state-core/src/main/java/org/incendo/state/MutableStateful.java b/state-core/src/main/java/org/incendo/state/MutableStateful.java index bd87f00..678d568 100644 --- a/state-core/src/main/java/org/incendo/state/MutableStateful.java +++ b/state-core/src/main/java/org/incendo/state/MutableStateful.java @@ -25,26 +25,28 @@ import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.returnsreceiver.qual.This; /** * Something that holds a {@link State} that can be mutated. * *

It is recommended to extend {@link AbstractStateful} when implementing this interface.

* - * @param state type + * @param state type + * @param self-referencing type * @since 1.0.0 */ @API(status = API.Status.STABLE, since = "1.0.0") -public interface MutableStateful> extends Stateful { +public interface MutableStateful, V extends MutableStateful> extends Stateful { /** * Creates a new mutable stateful instance with the given {@code initialState}. * - * @param state type + * @param state type * @param initialState initial state * @return the instance */ - static > @NonNull MutableStateful of(final @NonNull S initialState) { + static > @NonNull MutableStateful of(final @NonNull U initialState) { return new StatefulImpl<>(initialState); } @@ -53,20 +55,32 @@ public interface MutableStateful> extends Stateful { * * @param state new state * @throws IllegalStateTransitionException if the state transition is not possible + * @return {@code this} */ - void transitionTo(@NonNull S state) throws IllegalStateTransitionException; + @This @NonNull V transitionTo(@NonNull U state) throws IllegalStateTransitionException; /** * Attempts to transition from the given {@code currentState} to the given {@code newState}. * * @param currentState expected current state, the state transition will fail if the current {@link #state()} is different * @param newState new state + * @return {@code this} * @throws UnexpectedStateException if the actual {@link #state()} is different form the {@code currentState} * @throws IllegalStateTransitionException if the state transition is not possible */ - void transition(@NonNull S currentState, @NonNull S newState) throws + @This @NonNull V transition(@NonNull U currentState, @NonNull U newState) throws UnexpectedStateException, IllegalStateTransitionException; + /** + * Creates a new interaction builder. + * + * @return the builder + */ + @SuppressWarnings("unchecked") + default StateInteraction.@NonNull Builder interact() { + return StateInteraction.on((V) this); + } + /** * Returns an immutable view of this instance. * @@ -74,12 +88,12 @@ void transition(@NonNull S currentState, @NonNull S newState) throws * * @return immutable view */ - default @NonNull Stateful asImmutable() { - final Stateful mutable = this; - return new Stateful() { + default @NonNull Stateful asImmutable() { + final Stateful mutable = this; + return new Stateful() { @Override - public @NonNull S state() { + public @NonNull U state() { return mutable.state(); } }; diff --git a/state-core/src/main/java/org/incendo/state/State.java b/state-core/src/main/java/org/incendo/state/State.java index b50b0ed..979083a 100644 --- a/state-core/src/main/java/org/incendo/state/State.java +++ b/state-core/src/main/java/org/incendo/state/State.java @@ -35,7 +35,7 @@ * @since 1.0.0 */ @API(status = API.Status.STABLE, since = "1.0.0") -public interface State> extends Stateful { +public interface State> extends Stateful { /** * Returns the transitions that are possible from this state. diff --git a/state-core/src/main/java/org/incendo/state/StateInteraction.java b/state-core/src/main/java/org/incendo/state/StateInteraction.java new file mode 100644 index 0000000..c0bb1f4 --- /dev/null +++ b/state-core/src/main/java/org/incendo/state/StateInteraction.java @@ -0,0 +1,144 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.state; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import org.apiguardian.api.API; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.returnsreceiver.qual.This; + +@API(status = API.Status.STABLE, since = "1.0.0") +public interface StateInteraction, V extends Stateful> { + + /** + * Creates a new interaction builder. + * + * @param state type + * @param instance type + * @param instance instance to run the interaction on + * @return interaction builder + */ + static , V extends Stateful> @NonNull Builder on(final @NonNull V instance) { + return new Builder<>(instance); + } + + /** + * Executes the interaction. + * + * @return the result of the interaction + */ + @NonNull V execute(); + + final class Builder, V extends Stateful> implements StateInteraction { + + private final V instance; + + private States incomingStates; + private States outgoingStates; + private States shortCircuitStates; + private Function interaction; + + private Builder(final @NonNull V instance) { + this.instance = Objects.requireNonNull(instance, "instance"); + this.incomingStates = States.of(instance.state()); + this.outgoingStates = instance.allowedTransitions(); + this.shortCircuitStates = States.empty(); + this.interaction = UnaryOperator.identity(); + } + + /** + * Sets the allowed incoming states of the interaction. + * + * @param states incoming states + * @return {@code this} + */ + public @This @NonNull Builder incomingStates(final @NonNull States states) { + this.incomingStates = Objects.requireNonNull(states, "states"); + return this; + } + + /** + * Sets the allowed outgoing states of the interaction. + * + * @param states outgoing states + * @return {@code this} + */ + public @This @NonNull Builder outgoingStates(final @NonNull States states) { + this.outgoingStates = Objects.requireNonNull(states, "states"); + return this; + } + + /** + * Sets the short-circuit states of the interaction. + * + *

If the instance has a short-circuit state then bot the incoming & outgoing state validation will be + * bypassed, and the incoming instance will be immediately returned.

+ * + * @param states short-circuit states + * @return {@code this} + */ + public @This @NonNull Builder shortCircuitStates(final @NonNull States states) { + this.shortCircuitStates = Objects.requireNonNull(states, "states"); + return this; + } + + /** + * Sets the interaction. + * + * @param interaction interaction + * @return {@code this} + */ + public @This @NonNull Builder interaction(final @NonNull Function interaction) { + this.interaction = Objects.requireNonNull(interaction, "interaction"); + return this; + } + + /** + * Builds the interaction. + * + * @return the interaction + */ + public @NonNull StateInteraction build() { + return new StateInteractionImpl<>( + this.instance, + this.incomingStates, + this.outgoingStates, + this.shortCircuitStates, + this.interaction + ); + } + + /** + * {@link #build() Builds} and {@link StateInteraction#execute() executes} the interaction. + * + * @return the result of the interaction + */ + @Override + public @NonNull V execute() { + return this.build().execute(); + } + } +} diff --git a/state-core/src/main/java/org/incendo/state/StateInteractionImpl.java b/state-core/src/main/java/org/incendo/state/StateInteractionImpl.java new file mode 100644 index 0000000..8b10bdf --- /dev/null +++ b/state-core/src/main/java/org/incendo/state/StateInteractionImpl.java @@ -0,0 +1,59 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.state; + +import java.util.function.Function; +import org.apiguardian.api.API; +import org.checkerframework.checker.nullness.qual.NonNull; + +@API(status = API.Status.STABLE, since = "1.0.0") +record StateInteractionImpl, V extends Stateful>( + @NonNull V instance, + @NonNull States incomingStates, + @NonNull States outgoingStates, + @NonNull States shortcircuitStates, + @NonNull Function interaction +) implements StateInteraction { + + @Override + public @NonNull V execute() { + final U currentState = this.instance.state(); + if (!this.incomingStates.contains(currentState)) { + throw new UnexpectedStateException(this.incomingStates, currentState, this.instance); + } + + if (this.shortcircuitStates.contains(currentState)) { + return this.instance; + } + + final V result = this.interaction.apply(this.instance); + + final U newState = result.state(); + if (!this.outgoingStates.contains(newState)) { + throw new UnexpectedStateException(this.outgoingStates, newState, result); + } + + return result; + } +} diff --git a/state-core/src/main/java/org/incendo/state/Stateful.java b/state-core/src/main/java/org/incendo/state/Stateful.java index f2a9cec..253b2e1 100644 --- a/state-core/src/main/java/org/incendo/state/Stateful.java +++ b/state-core/src/main/java/org/incendo/state/Stateful.java @@ -25,30 +25,32 @@ import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.returnsreceiver.qual.This; /** * Something that holds a {@link State}. * - * @param state type + * @param state type + * @param self-referencing type * @since 1.0.0 */ @FunctionalInterface @API(status = API.Status.STABLE, since = "1.0.0") -public interface Stateful> { +public interface Stateful, V extends Stateful> { /** * Returns the current state. * * @return current state */ - @NonNull S state(); + @NonNull U state(); /** * Returns the transitions that are possible from the current {@link #state()}. * * @return allowed state transitions */ - default @NonNull States allowedTransitions() { + default @NonNull States allowedTransitions() { return this.state().allowedTransitions(); } @@ -58,7 +60,7 @@ public interface Stateful> { * @param state new state * @return {@code true} if the state transition is allowed, {@code false} if not */ - default boolean canTransitionTo(final @NonNull S state) { + default boolean canTransitionTo(final @NonNull U state) { return this.allowedTransitions().contains(state); } @@ -66,13 +68,15 @@ default boolean canTransitionTo(final @NonNull S state) { * Fails exceptionally if the current {@link #state()} is different from the given {@code state}. * * @param state expected state + * @return {@code this} * @throws UnexpectedStateException if the current {@link #state()} is different from the given {@code state} */ - default void expectState(final @NonNull S state) throws UnexpectedStateException { - final S currentState = this.state(); + @SuppressWarnings("unchecked") + default @This @NonNull V expectState(final @NonNull U state) throws UnexpectedStateException { + final U currentState = this.state(); if (currentState.equals(state)) { - return; + return (V) this; } - throw new UnexpectedStateException(currentState, state, this); + throw new UnexpectedStateException(States.of(state), currentState, this); } } diff --git a/state-core/src/main/java/org/incendo/state/StatefulImpl.java b/state-core/src/main/java/org/incendo/state/StatefulImpl.java index c9d309f..1c29846 100644 --- a/state-core/src/main/java/org/incendo/state/StatefulImpl.java +++ b/state-core/src/main/java/org/incendo/state/StatefulImpl.java @@ -27,7 +27,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @API(status = API.Status.INTERNAL, since = "1.0.0") -final class StatefulImpl> extends AbstractStateful { +final class StatefulImpl> extends AbstractStateful> { StatefulImpl(final @NonNull S initialState) { super(initialState); diff --git a/state-core/src/main/java/org/incendo/state/StatesImpl.java b/state-core/src/main/java/org/incendo/state/StatesImpl.java index 7a3ebe9..f903633 100644 --- a/state-core/src/main/java/org/incendo/state/StatesImpl.java +++ b/state-core/src/main/java/org/incendo/state/StatesImpl.java @@ -29,6 +29,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.NonNull; @@ -54,4 +55,9 @@ public boolean contains(final @NonNull S state) { states.add(state); return new StatesImpl<>(Collections.unmodifiableCollection(states)); } + + @Override + public String toString() { + return this.states.stream().map(S::toString).collect(Collectors.joining(", ", "(", ")")); + } } diff --git a/state-core/src/main/java/org/incendo/state/UnexpectedStateException.java b/state-core/src/main/java/org/incendo/state/UnexpectedStateException.java index 1dfcb66..e586a5d 100644 --- a/state-core/src/main/java/org/incendo/state/UnexpectedStateException.java +++ b/state-core/src/main/java/org/incendo/state/UnexpectedStateException.java @@ -34,9 +34,9 @@ @API(status = API.Status.STABLE, since = "1.0.0") public class UnexpectedStateException extends IllegalArgumentException { - private final State expected; + private final States expected; private final State actual; - private final Stateful stateful; + private final Stateful stateful; /** * Creates a new instance. @@ -46,11 +46,11 @@ public class UnexpectedStateException extends IllegalArgumentException { * @param stateful instance that holds the state */ public UnexpectedStateException( - final @NonNull State expected, + final @NonNull States expected, final @NonNull State actual, - final @NonNull Stateful stateful + final @NonNull Stateful stateful ) { - super(String.format("Expected state '%s' but was '%s'", expected, actual)); + super(String.format("Expected states '%s' but was '%s'", expected, actual)); this.expected = expected; this.actual = actual; this.stateful = stateful; @@ -61,7 +61,7 @@ public UnexpectedStateException( * * @return expected state */ - public @NonNull State expected() { + public @NonNull States expected() { return this.expected; } @@ -79,7 +79,7 @@ public UnexpectedStateException( * * @return stateful instance */ - public @NonNull Stateful stateful() { + public @NonNull Stateful stateful() { return this.stateful; } } diff --git a/state-core/src/test/java/org/incendo/state/IntegrationTest.java b/state-core/src/test/java/org/incendo/state/IntegrationTest.java new file mode 100644 index 0000000..b38b9ee --- /dev/null +++ b/state-core/src/test/java/org/incendo/state/IntegrationTest.java @@ -0,0 +1,123 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package org.incendo.state; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.returnsreceiver.qual.This; +import org.junit.jupiter.api.Test; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +/** + * Test of multiple different components at this. This is not structured + * as a proper test, and instead serves as an example. + */ +class IntegrationTest { + + @Test + void test() { + // Create the stateful instance. + final TestObject object = new TestObject(); + + // Transition to foo. + this.foo(object); + // Transition to foo again. + this.foo(object); + + // We should only have foo'd once! + assertThat(object.fooCounter()).isEqualTo(1); + + // Attempting to end prematurely fails exceptionally. + assertThrows( + IllegalStateTransitionException.class, + () -> this.end(object) + ); + + // But we can transition into a state that can be ended. + object.transition(TestState.INTERMEDIARY_FOO, TestState.INTERMEDIARY_BAR); + + // ... and then we end! + assertThat(this.end(object).state()).isEqualTo(TestState.END_STATE); + } + + private @NonNull TestObject foo(final @NonNull TestObject object) { + return object.interact() + .incomingStates(States.of(TestState.START_STATE, TestState.INTERMEDIARY_FOO)) + .outgoingStates(States.of(TestState.INTERMEDIARY_FOO)) + .shortCircuitStates(States.of(TestState.INTERMEDIARY_FOO)) + .interaction(instance -> instance.foo().transitionTo(TestState.INTERMEDIARY_FOO)) + .execute(); + } + + private @NonNull TestObject end(final @NonNull TestObject object) { + return object.interact() + .outgoingStates(States.of(TestState.END_STATE)) + .shortCircuitStates(States.of(TestState.END_STATE)) + .interaction(instance -> instance.transitionTo(TestState.END_STATE)) + .execute(); + } + + enum TestState implements State { + START_STATE, + INTERMEDIARY_FOO, + INTERMEDIARY_BAR, + INTERMEDIARY_BAZ, + END_STATE; + + static { + // We cannot create these in the enum constructor because of illegal forward referencing. + START_STATE.allowedTransitions = States.ofEnum(INTERMEDIARY_FOO, END_STATE); + INTERMEDIARY_FOO.allowedTransitions = States.ofEnum(INTERMEDIARY_FOO, INTERMEDIARY_BAR); + INTERMEDIARY_BAR.allowedTransitions = States.of(INTERMEDIARY_BAZ, END_STATE); + INTERMEDIARY_BAZ.allowedTransitions = States.of(END_STATE); + END_STATE.allowedTransitions = States.empty(); + } + + private States allowedTransitions; + + @Override + public @NonNull States allowedTransitions() { + return States.lazy(() -> this.allowedTransitions); + } + } + + static final class TestObject extends AbstractStateful { + + private int fooCounter = 0; + + TestObject() { + super(TestState.START_STATE); + } + + @This @NonNull TestObject foo() { + this.fooCounter++; + return this; + } + + int fooCounter() { + return this.fooCounter; + } + } +}