// // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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 com.cloud.utils.fsm; import java.util.ArrayList; import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Specifically, it implements the Moore machine. * so someone else can add/modify states easily without regression. * business logic anyways. * * @param <S> state * @param <E> event */ public class StateMachine2<S, E, V extends StateObject<S>> { private final HashMap<S, StateEntry> _states = new HashMap<S, StateEntry>(); private final StateEntry _initialStateEntry; private List<StateListener<S, E, V>> _listeners = new ArrayList<StateListener<S, E, V>>(); public StateMachine2() { _initialStateEntry = new StateEntry(null); } public void addInitialTransition(E event, S toState) { addTransition(null, event, toState); } public void addTransition(S currentState, E event, S toState) { addTransition(new Transition<S, E>(currentState, event, toState, null)); } @SafeVarargs public final void addTransitionFromStates(E event, S toState, S... fromStates) { for (S fromState : fromStates) { addTransition(fromState, event, toState); } } public void addTransition(Transition<S, E> transition) { S currentState = transition.getCurrentState(); E event = transition.getEvent(); S toState = transition.getToState(); StateEntry entry = null; if (currentState == null) { entry = _initialStateEntry; } else { entry = _states.get(currentState); if (entry == null) { entry = new StateEntry(currentState); _states.put(currentState, entry); } } entry.addTransition(event, toState, transition); entry = _states.get(toState); if (entry == null) { entry = new StateEntry(toState); _states.put(toState, entry); } entry.addFromTransition(event, currentState); } public Set<E> getPossibleEvents(S s) { StateEntry entry = _states.get(s); return entry.nextStates.keySet(); } public S getNextState(S s, E e) throws NoTransitionException { return getTransition(s, e).getToState(); } public Transition<S, E> getTransition(S s, E e) throws NoTransitionException { StateEntry entry = null; if (s == null) { entry = _initialStateEntry; } else { entry = _states.get(s); assert entry != null : "Cannot retrieve transitions for state " + s; } Transition<S, E> transition = entry.nextStates.get(e); if (transition == null) { throw new NoTransitionException("Unable to transition to a new state from " + s + " via " + e); } return transition; } public List<S> getFromStates(S s, E e) { StateEntry entry = _states.get(s); if (entry == null) { return new ArrayList<S>(); } return entry.prevStates.get(e); } public boolean transitTo(V vo, E e, Object opaque, StateDao<S, E, V> dao) throws NoTransitionException { S currentState = vo.getState(); S nextState = getNextState(currentState, e); Transition<S, E> transition = getTransition(currentState, e); boolean transitionStatus = true; if (nextState == null) { transitionStatus = false; } for (StateListener<S, E, V> listener : _listeners) { listener.preStateTransitionEvent(currentState, e, nextState, vo, transitionStatus, opaque); } transitionStatus = dao.updateState(currentState, e, nextState, vo, opaque); if (!transitionStatus) { return false; } for (StateListener<S, E, V> listener : _listeners) { listener.postStateTransitionEvent(transition, vo, transitionStatus, opaque); } return true; } public boolean registerListener(StateListener<S, E, V> listener) { synchronized (_listeners) { return _listeners.add(listener); } } @Override public String toString() { StringBuilder str = new StringBuilder(1024); _initialStateEntry.buildString(str); for (StateEntry entry : _states.values()) { entry.buildString(str); } return str.toString(); } public static class Transition<S, E> { private S currentState; private E event; private S toState; private List<Impact> impacts; public static enum Impact { USAGE } public Transition(S currentState, E event, S toState, List<Impact> impacts) { this.currentState = currentState; this.event = event; this.toState = toState; this.impacts = impacts; } public S getCurrentState() { return currentState; } public E getEvent() { return event; } public S getToState() { return toState; } public boolean isImpacted(Impact impact) { if (impacts == null || impacts.isEmpty()) { return false; } return impacts.contains(impact); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Transition that = (Transition) o; if (currentState != null ? !currentState.equals(that.currentState) : that.currentState != null) return false; if (event != null ? !event.equals(that.event) : that.event != null) return false; if (toState != null ? !toState.equals(that.toState) : that.toState != null) return false; return true; } @Override public int hashCode() { int result = currentState != null ? currentState.hashCode() : 0; result = 31 * result + (event != null ? event.hashCode() : 0); result = 31 * result + (toState != null ? toState.hashCode() : 0); return result; } } private class StateEntry { public S state; public HashMap<E, Transition<S, E>> nextStates; public HashMap<E, List<S>> prevStates; public StateEntry(S state) { this.state = state; prevStates = new HashMap<E, List<S>>(); nextStates = new HashMap<E, Transition<S, E>>(); } public void addTransition(E e, S s, Transition<S, E> transition) { assert !nextStates.containsKey(e) : "State " + getStateStr() + " already contains a transition to state " + nextStates.get(e).toString() + " via event " + e.toString() + ". Please revisit the rule you're adding to state " + s.toString(); nextStates.put(e, transition); } public void addFromTransition(E e, S s) { List<S> l = prevStates.get(e); if (l == null) { l = new ArrayList<S>(); prevStates.put(e, l); } assert !l.contains(s) : "Already contains the from transition " + e.toString() + " from state " + s.toString() + " to " + getStateStr(); l.add(s); } protected String getStateStr() { return state == null ? "Initial" : state.toString(); } public void buildString(StringBuilder str) { str.append("State: ").append(getStateStr()).append("\n"); for (Map.Entry<E, Transition<S, E>> nextState : nextStates.entrySet()) { str.append(" --> Event: "); Formatter format = new Formatter(); str.append(format.format("%-30s", nextState.getKey().toString())); str.append("----> State: "); str.append(nextState.getValue().toString()); str.append("\n"); } } } }