| /* |
| * Copyright (c) 2007-2009, 2011, 2012, 2015 Eike Stepper (Berlin, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.net4j.util.fsm; |
| |
| import org.eclipse.net4j.internal.util.bundle.OM; |
| import org.eclipse.net4j.util.event.IEvent; |
| import org.eclipse.net4j.util.event.IListener; |
| import org.eclipse.net4j.util.event.INotifier; |
| import org.eclipse.net4j.util.lifecycle.Lifecycle; |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| |
| import java.text.MessageFormat; |
| |
| /** |
| * A <a href="http://en.wikipedia.org/wiki/Finite-state_machine">finite state machine</a> that is based on a matrix of |
| * {@link ITransition transitions}. |
| * <p> |
| * A finite state machine can fire the following events: |
| * <ul> |
| * <li> {@link StateChangedEvent} after state changes of a <i>subject</i>. |
| * </ul> |
| * |
| * @author Eike Stepper |
| * @apiviz.landmark |
| * @apiviz.has {@link ITransition} oneway - - matrix |
| * @apiviz.uses {@link FiniteStateMachine.StateChangedEvent} - - fires |
| */ |
| public abstract class FiniteStateMachine<STATE extends Enum<?>, EVENT extends Enum<?>, SUBJECT> extends Lifecycle |
| { |
| @SuppressWarnings("rawtypes") |
| public static final ITransition IGNORE = new InternalIgnoreTransition(); |
| |
| @SuppressWarnings("rawtypes") |
| public static final ITransition FAIL = new InternalFailTransition(); |
| |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, FiniteStateMachine.class); |
| |
| private static final String MSG_PROCESS = "Processing event {0} in state {1} for {2} (data={3})"; //$NON-NLS-1$ |
| |
| private static final String MSG_IGNORE = "Ignoring event {0} in state {1} for {2} (data={3})"; //$NON-NLS-1$ |
| |
| private static final String MSG_FAIL = "Failing event {0} in state {1} for {2} (data={3})"; //$NON-NLS-1$ |
| |
| private STATE[] states; |
| |
| private EVENT[] events; |
| |
| private ITransition<STATE, EVENT, SUBJECT, ?>[][] transitions; |
| |
| @SuppressWarnings("unchecked") |
| public FiniteStateMachine(Class<STATE> stateEnum, Class<EVENT> eventEnum, |
| ITransition<STATE, EVENT, SUBJECT, ?> defaultTransition) |
| { |
| states = stateEnum.getEnumConstants(); |
| events = eventEnum.getEnumConstants(); |
| transitions = new ITransition[states.length][events.length]; |
| initAll(defaultTransition); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public FiniteStateMachine(Class<STATE> stateEnum, Class<EVENT> eventEnum) |
| { |
| this(stateEnum, eventEnum, FAIL); |
| } |
| |
| public final STATE[] getStates() |
| { |
| return states; |
| } |
| |
| public final EVENT[] getEvents() |
| { |
| return events; |
| } |
| |
| public final ITransition<STATE, EVENT, SUBJECT, ?> getTransition(STATE state, EVENT event) |
| { |
| int s = state.ordinal(); |
| int e = event.ordinal(); |
| return transitions[s][e]; |
| } |
| |
| public final void init(STATE state, EVENT event, STATE targetState) |
| { |
| init(state, event, new ChangeStateTransition(targetState)); |
| } |
| |
| public final void init(STATE state, EVENT event, ITransition<STATE, EVENT, SUBJECT, ?> transition) |
| { |
| checkTransition(transition); |
| int s = state.ordinal(); |
| int e = event.ordinal(); |
| transitions[s][e] = transition; |
| } |
| |
| public final void initEvents(STATE state, STATE targetState) |
| { |
| initEvents(state, new ChangeStateTransition(targetState)); |
| } |
| |
| public final void initEvents(STATE state, ITransition<STATE, EVENT, SUBJECT, ?> transition) |
| { |
| checkTransition(transition); |
| int s = state.ordinal(); |
| for (int e = 0; e < events.length; e++) |
| { |
| transitions[s][e] = transition; |
| } |
| } |
| |
| public final void initStates(EVENT event, STATE targetState) |
| { |
| initStates(event, new ChangeStateTransition(targetState)); |
| } |
| |
| public final void initStates(EVENT event, ITransition<STATE, EVENT, SUBJECT, ?> transition) |
| { |
| checkTransition(transition); |
| int e = event.ordinal(); |
| for (int s = 0; s < states.length; s++) |
| { |
| transitions[s][e] = transition; |
| } |
| } |
| |
| public final void initAll(STATE targetState) |
| { |
| initAll(new ChangeStateTransition(targetState)); |
| } |
| |
| public final void initAll(ITransition<STATE, EVENT, SUBJECT, ?> transition) |
| { |
| checkTransition(transition); |
| for (int s = 0; s < states.length; s++) |
| { |
| for (int e = 0; e < events.length; e++) |
| { |
| transitions[s][e] = transition; |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public final <DATA> void process(SUBJECT subject, EVENT event, DATA data) |
| { |
| STATE state = getState(subject); |
| int s = state.ordinal(); |
| int e = event.ordinal(); |
| ITransition<STATE, EVENT, SUBJECT, DATA> transition = (ITransition<STATE, EVENT, SUBJECT, DATA>)transitions[s][e]; |
| if (transition == IGNORE) |
| { |
| // Do nothing |
| } |
| else if (transition == FAIL) |
| { |
| throw new IllegalStateException(formatFailMessage(subject, state, event, data)); |
| } |
| else |
| { |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(formatProcessMessage(subject, state, event, data)); |
| } |
| |
| transition.execute(subject, state, event, data); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected ITransition<STATE, EVENT, SUBJECT, ?> createIgnoreTransition(STATE state, EVENT event) |
| { |
| return IGNORE; |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected ITransition<STATE, EVENT, SUBJECT, ?> createFailTransition(STATE state, EVENT event) |
| { |
| return FAIL; |
| } |
| |
| protected String formatProcessMessage(SUBJECT subject, STATE state, EVENT event, Object data) |
| { |
| return MessageFormat.format(MSG_PROCESS, event, state, subject, data); |
| } |
| |
| protected String formatIgnoreMessage(SUBJECT subject, STATE state, EVENT event, Object data) |
| { |
| return MessageFormat.format(MSG_IGNORE, event, state, subject, data); |
| } |
| |
| protected String formatFailMessage(SUBJECT subject, STATE state, EVENT event, Object data) |
| { |
| return MessageFormat.format(MSG_FAIL, event, state, subject, data); |
| } |
| |
| protected abstract STATE getState(SUBJECT subject); |
| |
| protected abstract void setState(SUBJECT subject, STATE state); |
| |
| /** |
| * @since 3.0 |
| */ |
| protected STATE changeState(SUBJECT subject, STATE state) |
| { |
| STATE oldState = getState(subject); |
| if (oldState != state) |
| { |
| setState(subject, state); |
| IListener[] listeners = getListeners(); |
| if (listeners != null) |
| { |
| fireEvent(new StateChangedEvent(subject, oldState, state), listeners); |
| } |
| |
| return oldState; |
| } |
| |
| return null; |
| } |
| |
| private void checkTransition(ITransition<STATE, EVENT, SUBJECT, ?> transition) |
| { |
| if (transition == null) |
| { |
| throw new IllegalArgumentException("transition == null"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * A {@link ITransition transition} that does nothing. |
| * |
| * @author Eike Stepper |
| * @deprecated Use {@link FiniteStateMachine#IGNORE} |
| * @apiviz.exclude |
| */ |
| @Deprecated |
| public static class IgnoreTransition implements ITransition<Enum<?>, Enum<?>, Object, Object> |
| { |
| public void execute(Object subject, Enum<?> state, Enum<?> event, Object data) |
| { |
| // Do nothing |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "IGNORE"; //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * A {@link ITransition transition} that throws an {@link IllegalStateException}. |
| * |
| * @author Eike Stepper |
| * @deprecated Use {@link FiniteStateMachine#FAIL} |
| * @apiviz.exclude |
| */ |
| @Deprecated |
| public static class FailTransition implements ITransition<Enum<?>, Enum<?>, Object, Object> |
| { |
| public void execute(Object subject, Enum<?> state, Enum<?> event, Object data) |
| { |
| // Do nothing |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "FAIL"; //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * A {@link ITransition transition} that does nothing. |
| * |
| * @author Eike Stepper |
| * @apiviz.exclude |
| */ |
| private static class InternalIgnoreTransition implements ITransition<Enum<?>, Enum<?>, Object, Object> |
| { |
| public void execute(Object subject, Enum<?> state, Enum<?> event, Object data) |
| { |
| // Do nothing |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "IGNORE"; //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * A {@link ITransition transition} that throws an {@link IllegalStateException}. |
| * |
| * @author Eike Stepper |
| * @apiviz.exclude |
| */ |
| private static class InternalFailTransition implements ITransition<Enum<?>, Enum<?>, Object, Object> |
| { |
| public void execute(Object subject, Enum<?> state, Enum<?> event, Object data) |
| { |
| // Do nothing |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "FAIL"; //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * A {@link ITransition transition} that changes the {@link #getTargetState() state} of a <i>subject</i>. |
| * |
| * @author Eike Stepper |
| */ |
| public class ChangeStateTransition implements ITransition<STATE, EVENT, SUBJECT, Object> |
| { |
| private STATE targetState; |
| |
| public ChangeStateTransition(STATE targetState) |
| { |
| this.targetState = targetState; |
| } |
| |
| public STATE getTargetState() |
| { |
| return targetState; |
| } |
| |
| public void execute(SUBJECT subject, STATE state, EVENT event, Object data) |
| { |
| changeState(subject, targetState); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("CHANGE_STATE[{0}]", targetState); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public class StateChangedEvent implements IEvent |
| { |
| private Object subject; |
| |
| private Enum<?> oldState; |
| |
| private Enum<?> newState; |
| |
| public StateChangedEvent(Object subject, Enum<?> oldState, Enum<?> newState) |
| { |
| this.subject = subject; |
| this.oldState = oldState; |
| this.newState = newState; |
| } |
| |
| public INotifier getSource() |
| { |
| return FiniteStateMachine.this; |
| } |
| |
| public Object getSubject() |
| { |
| return subject; |
| } |
| |
| public Enum<?> getOldState() |
| { |
| return oldState; |
| } |
| |
| public Enum<?> getNewState() |
| { |
| return newState; |
| } |
| } |
| } |