blob: 4155af67413f0551a368cdb23b046497e8c8e711 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.support.v17.leanback.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Linear or DAG of {@link State}s. StateMachine is by default a linear model, until
* {@link #addState(State, State)} is called. Each State has three status:
* STATUS_ZERO, STATUS_INVOKED, STATUS_EXECUTED. We allow client to run a State, which will
* put State in STATUS_INVOKED. A State will be executed when prior States are executed and
* Precondition for this State is true, then the State will be marked as STATUS_EXECUTED.
*
* @hide
*/
public final class StateMachine {
/**
* No request on the State
*/
public static final int STATUS_ZERO = 0;
/**
* Somebody wants to run the state but not yet executed because either the condition is
* false or lower States are not executed.
*/
public static final int STATUS_INVOKED = 1;
/**
* Somebody wants to run the State and the State was executed.
*/
public static final int STATUS_EXECUTED = 2;
public static class State {
private int mStatus;
private ArrayList<State> mPriorStates;
/**
* Run State, Subclass may override.
*/
public void run() {
}
/**
* Returns true if State can run, false otherwise. Subclass may override.
* @return True if State can run, false otherwise. Subclass may override.
*/
public boolean canRun() {
return true;
}
/**
* @return True if the State has been executed.
*/
final boolean runIfNeeded() {
if (mStatus!= STATUS_EXECUTED) {
if (mStatus == STATUS_INVOKED && canRun()) {
run();
mStatus = STATUS_EXECUTED;
} else {
return false;
}
}
return true;
}
void addPriorState(State state) {
if (mPriorStates == null) {
mPriorStates = new ArrayList<State>();
}
if (!mPriorStates.contains(state)) {
mPriorStates.add(state);
}
}
final void markInvoked() {
if (mStatus == STATUS_ZERO) {
mStatus = STATUS_INVOKED;
}
}
final void updateStatus(int status) {
mStatus = status;
}
/**
* Get status, return one of {@link #STATUS_ZERO}, {@link #STATUS_INVOKED},
* {@link #STATUS_EXECUTED}.
* @return Status of the State.
*/
public final int getStatus() {
return mStatus;
}
@Override
public final boolean equals(Object other) {
return this == other;
}
}
private boolean mSorted = true;
private final ArrayList<State> mSortedList = new ArrayList<State>();
/**
* Add a State to StateMachine, ignore if it is already added.
* @param state The state to add.
*/
public void addState(State state) {
if (!mSortedList.contains(state)) {
state.updateStatus(STATUS_ZERO);
mSortedList.add(state);
}
}
/**
* Add two States to StateMachine and create an edge between this two.
* StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
* sort() is required to sort the Direct acyclic graph.
* @param fromState The from state to add.
* @param toState The to state to add.
*/
public void addState(State fromState, State toState) {
addState(fromState);
addState(toState);
toState.addPriorState(fromState);
mSorted = false;
}
void verifySorted() {
if (!mSorted) {
throw new RuntimeException("Graph not sorted");
}
}
public void runState(State state) {
verifySorted();
state.markInvoked();
runPendingStates();
}
public void runPendingStates() {
verifySorted();
for (int i = 0, size = mSortedList.size(); i < size; i++) {
if (!mSortedList.get(i).runIfNeeded()) {
break;
}
}
}
public void resetStatus() {
for (int i = 0, size = mSortedList.size(); i < size; i++) {
mSortedList.get(i).updateStatus(STATUS_ZERO);
}
}
/**
* StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
* sort() is required to sort the Direct acyclic graph.
*/
public void sort() {
if (mSorted) {
return;
}
// L: Empty list that will contain the sorted States
ArrayList<State> L = new ArrayList<State>();
// S: Set of all nodes with no incoming edges
ArrayList<State> S = new ArrayList<State>();
HashMap<State, ArrayList<State>> edges = new HashMap<State, ArrayList<State>>();
for (int i = mSortedList.size() - 1; i >= 0 ; i--) {
State state = mSortedList.get(i);
if (state.mPriorStates != null && state.mPriorStates.size() > 0) {
edges.put(state, new ArrayList<State>(state.mPriorStates));
} else {
S.add(state);
}
}
while (!S.isEmpty()) {
// remove a State without incoming Node from S, add to L
State state = S.remove(S.size() - 1);
L.add(state);
// for each toState that having an incoming edge from "state":
for (Iterator<Map.Entry<State, ArrayList<State>>> iterator =
edges.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<State, ArrayList<State>> entry = iterator.next();
ArrayList<State> fromStates = entry.getValue();
// remove edge from graph
if (fromStates.remove(state)) {
if (fromStates.size() == 0) {
State toState = entry.getKey();
// insert the toState to S if it has no more incoming edges
S.add(toState);
iterator.remove();
}
}
}
}
if (edges.size() > 0) {
throw new RuntimeException("Cycle in Graph");
}
mSortedList.clear();
mSortedList.addAll(L);
mSorted = true;
}
}