com.tangosol.util.fsm.AnnotationDrivenModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.util.fsm;
import com.tangosol.util.fsm.annotations.OnEnterState;
import com.tangosol.util.fsm.annotations.OnExitState;
import com.tangosol.util.fsm.annotations.OnTransition;
import com.tangosol.util.fsm.annotations.Transitions;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.EnumMap;
import java.util.Map;
/**
* An {@link AnnotationDrivenModel} is a {@link Model} created through extracting
* information defined by {@link Transitions} annotation.
*
* @param the type of the state for the {@link AnnotationDrivenModel}
*
* @author Brian Oliver
* @since Coherence 12.2.1
*/
public class AnnotationDrivenModel>
implements Model
{
// ----- AnnotationDrivenModel constructor ------------------------------
/**
* Constructs an {@link AnnotationDrivenModel} based on the specified
* annotated class.
*
* @param clzState the {@link Class} of the state of the {@link Transitions}
* @param oInstance the instance from which the {@link Model} will be
* reflected
*/
public AnnotationDrivenModel(Class clzState, Object oInstance)
{
if (oInstance == null)
{
throw new IllegalArgumentException(String.format("Can't create an %s from a null instance",
this.getClass().getName()));
}
else
{
// construct the model based on the annotations
Class> clzInstance = oInstance.getClass();
m_model = new SimpleModel(clzState);
// define maps to hold the reflected transition information
EnumMap> mapTransitionNames = new EnumMap>(clzState);
EnumMap>> mapTransitionActions = new EnumMap>>(clzState);
for (S state : m_model.getStates())
{
mapTransitionNames.put(state, new EnumMap(clzState));
mapTransitionActions.put(state, new EnumMap>(clzState));
}
// reflect out the defined transitions
Transitions annTransitions = clzInstance.getAnnotation(Transitions.class);
if (annTransitions != null)
{
// determine the valid state transitions based on the
// @Transitions annotation in the specified class
for (com.tangosol.util.fsm.annotations.Transition annTransition :
annTransitions.value())
{
String sProvidedTransitionName = annTransition.name();
// ensure the transition name is null if it wasn't specified
sProvidedTransitionName = sProvidedTransitionName == null
|| sProvidedTransitionName.trim().isEmpty() ? null
: sProvidedTransitionName;
// determine the ending state for the transition
String sStateToName = annTransition.toState();
S stateTo = m_model.getState(sStateToName);
if (stateTo == null)
{
throw new IllegalArgumentException(String.format(
"The %s defined on %s declares a to state %s that is not defined by %s.",
annTransition, clzInstance, sStateToName, clzState));
}
// determine the starting states for the transition
for (String sStateFromName : annTransition.fromStates())
{
// determine the starting states for the transition
S stateFrom = m_model.getState(sStateFromName);
if (stateFrom == null)
{
throw new IllegalArgumentException(String.format(
"The %s defined on %s declares a from state %s that is not defined by %s.",
annTransition, clzInstance, sStateFromName, clzState));
}
else
{
// ensure we have a transition name
String sTransitionName = sProvidedTransitionName == null
? String.format("%s to %s", stateFrom.name(), stateTo.name())
: sProvidedTransitionName;
// define the transition
mapTransitionNames.get(stateFrom).put(stateTo, sTransitionName);
}
}
}
}
// reflect and add state entry, exit and transition actions from
// annotated methods into the model
for (Method method : clzInstance.getMethods())
{
// add the StateEntryAction for the method (if annotated)
OnEnterState annOnEnterState = method.getAnnotation(OnEnterState.class);
if (annOnEnterState != null)
{
S state = m_model.getState(annOnEnterState.value());
if (state == null)
{
throw new IllegalArgumentException(String.format(
"The %s annotation on method %s defined in %s declares the state %s that " +
"is not defined in %s.",
annOnEnterState, method, clzInstance, annOnEnterState.value(), clzState));
}
// ensure the method has the correct signature for the
if (ReflectionHelper.isCompatibleMethod(method, Modifier.PUBLIC, Instruction.class, clzState,
clzState, Event.class, ExecutionContext.class))
{
// register the StateEntryAction
m_model.addStateEntryAction(state, new StateEntryActionMethod(oInstance, method));
}
else
{
throw new IllegalArgumentException(String.format(
"The method %s defined in class %s annotated with %s is not compatible with the " +
"required method signature 'Instruction method(State, State, Context);'.",
method, clzInstance, annOnEnterState));
}
}
// add the StateEntryAction for the method (if annotated)
OnExitState annOnExitState = method.getAnnotation(OnExitState.class);
if (annOnExitState != null)
{
// determine the state
S state = m_model.getState(annOnExitState.value());
if (state == null)
{
throw new IllegalArgumentException(String.format(
"The %s annotation on method %s defined in %s declares the state %s " +
"that is not defined in %s.",
annOnExitState, method, clzInstance, annOnExitState.value(), clzState));
}
// ensure the method has the correct signature for the
if (ReflectionHelper.isCompatibleMethod(method, Modifier.PUBLIC, Void.TYPE, clzState, Event.class,
ExecutionContext.class))
{
// register the StateExitAction
m_model.addStateExitAction(state, new StateExitActionMethod(oInstance, method));
}
else
{
throw new IllegalArgumentException(String.format(
"The method %s defined in class %s annotated with %s is not compatible with the " +
"required method signature 'void method(State, Context);'.",
method, clzInstance, annOnExitState));
}
}
// add the TransitionAction for the method (if annotated)
OnTransition annOnTransition = method.getAnnotation(OnTransition.class);
if (annOnTransition != null)
{
// ensure that the method has the correct signature
if (ReflectionHelper.isCompatibleMethod(method, Modifier.PUBLIC, Void.TYPE,
String.class, clzState, clzState, Event.class, ExecutionContext.class))
{
TransitionAction action = new TransitionActionMethod(oInstance, method);
// add the action for each of the transitions
for (String sStateFromName : annOnTransition.fromStates())
{
// determine the starting state for the transition
S stateFrom = m_model.getState(sStateFromName);
if (stateFrom == null)
{
throw new IllegalArgumentException(String.format(
"The %s defined on method %s in %s declares a from state %s that " +
"is not defined by %s.",
annOnTransition, method, clzInstance, sStateFromName, clzState));
}
else
{
for (String sStateToName : annOnTransition.toStates())
{
// determine the ending state for the transition
S stateTo = m_model.getState(sStateToName);
if (stateTo == null)
{
throw new IllegalArgumentException(String.format(
"The %s defined on method %s in %s declares a from state %s that " +
"is not defined by %s.",
annOnTransition, method, clzInstance, sStateToName, clzState));
}
else if (mapTransitionNames.get(stateFrom).containsKey(stateTo))
{
// add the transition action to the transition
mapTransitionActions.get(stateFrom).put(stateTo, action);
}
else
{
throw new IllegalArgumentException(String.format(
"The %s defined on method %s in %s specifies a transition from " +
"%s to %s that is not defined by the Finite State Machine.",
annOnTransition, method, clzInstance, stateFrom, stateTo));
}
}
}
}
}
else
{
throw new IllegalArgumentException(String.format(
"The method %s defined in class %s annotated with %s is not compatible with the " +
"required method signature 'void method(String, State, State, " +
" Event, Context);'.",
method, clzInstance, annOnTransition));
}
}
}
// define the transitions on the model
for (S stateFrom : m_model.getStates())
{
for (S stateTo : mapTransitionNames.keySet())
{
String sTransitionName = mapTransitionNames.get(stateFrom).get(stateTo);
if (sTransitionName != null)
{
TransitionAction action = mapTransitionActions.get(stateFrom).get(stateTo);
m_model.addTransition(new Transition(sTransitionName, stateFrom, stateTo, action));
}
}
}
}
}
// ----- Model interface ------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public Class getStateClass()
{
return m_model.getStateClass();
}
/**
* {@inheritDoc}
*/
@Override
public Map> getStateEntryActions()
{
return m_model.getStateEntryActions();
}
/**
* {@inheritDoc}
*/
@Override
public Map> getStateExitActions()
{
return m_model.getStateExitActions();
}
/**
* {@inheritDoc}
*/
@Override
public S[] getStates()
{
return m_model.getStates();
}
/**
* {@inheritDoc}
*/
@Override
public Iterable> getTransitions()
{
return m_model.getTransitions();
}
// ----- inner class StateEntryActionMethod -----------------------------
/**
* A {@link StateEntryActionMethod} is an {@link StateEntryAction}
* implementation based on a specified {@link Method} and instance.
*/
private static class StateEntryActionMethod>
implements StateEntryAction
{
/**
* Constructs an {@link StateEntryActionMethod}.
*
* @param oInstance the instance on which to invoke the {@link StateEntryAction}
* @param method the method to use for performing the {@link StateEntryAction}
*/
public StateEntryActionMethod(Object oInstance, Method method)
{
m_oInstance = oInstance;
m_method = method;
}
/**
* {@inheritDoc}
*/
public Instruction onEnterState(S previousState, S newState, Event event, ExecutionContext context)
{
try
{
return (Instruction) m_method.invoke(m_oInstance, previousState, newState, event, context);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
* The instance on which to invoke the methods.
*/
private Object m_oInstance;
/**
* The {@link Method} to execute for the {@link StateEntryAction}.
*/
private Method m_method;
}
// ----- inner class StateExitActionMethod ------------------------------
/**
* A {@link StateExitActionMethod} is an {@link StateExitAction}
* implementation based on a specified {@link Method} and instance.
*/
private static class StateExitActionMethod>
implements StateExitAction
{
/**
* Constructs an {@link StateExitActionMethod}.
*
* @param oInstance the instance on which to invoke the {@link StateEntryAction}
* @param method the method to use for performing the {@link StateEntryAction}
*/
public StateExitActionMethod(Object oInstance, Method method)
{
m_oInstance = oInstance;
m_method = method;
}
/**
* {@inheritDoc}
*/
public void onExitState(S state, Event event, ExecutionContext context)
{
try
{
m_method.invoke(m_oInstance, state, event, context);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
* The instance on which to invoke the methods.
*/
private Object m_oInstance;
/**
* The {@link Method} to execute for the {@link StateEntryAction}.
*/
private Method m_method;
}
// ----- inner class TransitionActionMethod -----------------------------
/**
* An {@link TransitionActionMethod} is an {@link TransitionAction}
* implementation based on a specified {@link Method} and instance.
*/
private static class TransitionActionMethod>
implements TransitionAction
{
/**
* Constructs an {@link TransitionActionMethod}.
*
* @param oInstance the instance on which to invoke the {@link TransitionAction}
* @param method the method to use for performing the {@link TransitionAction}
*/
public TransitionActionMethod(Object oInstance, Method method)
{
m_oInstance = oInstance;
m_method = method;
}
/**
* {@inheritDoc}
*/
public void onTransition(String sTransitionName, S stateFrom, S stateTo, Event event,
ExecutionContext context)
throws RollbackTransitionException
{
try
{
m_method.invoke(m_oInstance, sTransitionName, stateFrom, stateTo, event, context);
}
catch (Exception e)
{
throw new RollbackTransitionException(stateFrom, stateTo, e);
}
}
// ----- data members -----------------------------------------------
/**
* The instance on which to invoke the methods.
*/
private Object m_oInstance;
/**
* The {@link Method} to execute for the {@link TransitionAction}.
*/
private Method m_method;
}
// ----- data members ---------------------------------------------------
/**
* The actual {@link Model}.
*/
private SimpleModel m_model;
}