SyncSM02: implement addState function
Duplicated state is not allowed.
Test: atest SynStateMachineTest
Change-Id: I0d5c73f666f90aebcfbf535cf0f824c5050941a2
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
index f2b0cfb..5a984c7 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
@@ -18,11 +18,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.util.State;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
/**
* An implementation of a state machine, meant to be called synchronously.
@@ -35,6 +39,7 @@
@NonNull private final String mName;
@NonNull private final Thread mMyThread;
private final boolean mDbg;
+ private final ArrayMap<State, StateInfo> mStateInfo = new ArrayMap<>();
// mCurrentState is the current state. mDestState is the target state that mCurrentState will
// transition to. The value of mDestState can be changed when a state processes a message and
@@ -91,9 +96,9 @@
}
/**
- * Add all of states to the state machine. Different StateInfos which have same state but have
- * different parents are not allowed. A state can not have multiple parent states.
- * This can only be called once either from mMyThread or before mMyThread started.
+ * Add all of states to the state machine. Different StateInfos which have same state are not
+ * allowed. In other words, a state can not have multiple parent states. #addAllStates can
+ * only be called once either from mMyThread or before mMyThread started.
*/
public final void addAllStates(@NonNull final List<StateInfo> stateInfos) {
ensureCorrectOrNotStartedThread();
@@ -101,13 +106,39 @@
if (mCurrentState != null) {
throw new IllegalStateException("State only can be added before started");
}
+
+ if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed");
+
+ if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured");
+
+ final Set<Class> usedClasses = new ArraySet<>();
+ for (final StateInfo info : stateInfos) {
+ Objects.requireNonNull(info.state);
+ if (!usedClasses.add(info.state.getClass())) {
+ throw new IllegalStateException("Adding the same state multiple times in a state "
+ + "machine is forbidden because it tends to be confusing; it can be done "
+ + "with anonymous subclasses but consider carefully whether you want to "
+ + "use a single state or other alternatives instead.");
+ }
+
+ mStateInfo.put(info.state, info);
+ }
+
+ // Check whether all of parent states indicated from StateInfo are added.
+ for (final StateInfo info : stateInfos) {
+ if (info.parent != null) ensureExistingState(info.parent);
+ }
}
/**
* Start the state machine. The initial state can't be child state.
+ *
+ * @param initialState the first state of this machine. The state must be exact state object
+ * setting up by {@link #addAllStates}, not a copy of it.
*/
public final void start(@NonNull final State initialState) {
ensureCorrectThread();
+ ensureExistingState(initialState);
mCurrentState = initialState;
mDestState = initialState;
@@ -127,11 +158,13 @@
* This function can NOT be called inside the State enter and exit function. The transition
* target is always defined and can never be changed mid-way of state transition.
*
- * @param destState will be the state to transition to.
+ * @param destState will be the state to transition to. The state must be the same instance set
+ * up by {@link #addAllStates}, not a copy of it.
*/
public final void transitionTo(@NonNull final State destState) {
if (mDbg) Log.d(mName, "transitionTo " + destState);
ensureCorrectThread();
+ ensureExistingState(destState);
if (mDestState == mCurrentState) {
mDestState = destState;
@@ -151,4 +184,8 @@
ensureCorrectThread();
}
+
+ private void ensureExistingState(@NonNull final State state) {
+ if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state");
+ }
}