package no.hal.scxml.generator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import no.hal.scxml.scxmlxt.AbstractState;
import no.hal.scxml.scxmlxt.AbstractTransition;
import no.hal.scxml.scxmlxt.AbstractTransitionEvent;
import no.hal.scxml.scxmlxt.AbstractUriLiteral;
import no.hal.scxml.scxmlxt.Action;
import no.hal.scxml.scxmlxt.AssignmentAction;
import no.hal.scxml.scxmlxt.BooleanLiteral;
import no.hal.scxml.scxmlxt.Condition;
import no.hal.scxml.scxmlxt.DelayLiteral;
import no.hal.scxml.scxmlxt.EObjectReference;
import no.hal.scxml.scxmlxt.EObjectUriLiteral;
import no.hal.scxml.scxmlxt.EnterEvent;
import no.hal.scxml.scxmlxt.Event;
import no.hal.scxml.scxmlxt.ExitEvent;
import no.hal.scxml.scxmlxt.Expression;
import no.hal.scxml.scxmlxt.FloatLiteral;
import no.hal.scxml.scxmlxt.InitialTransition;
import no.hal.scxml.scxmlxt.IntLiteral;
import no.hal.scxml.scxmlxt.InternalTransition;
import no.hal.scxml.scxmlxt.ResourceUriLiteral;
import no.hal.scxml.scxmlxt.ScriptAction;
import no.hal.scxml.scxmlxt.ScriptEvent;
import no.hal.scxml.scxmlxt.ScriptExpression;
import no.hal.scxml.scxmlxt.ScxmlxtFactory;
import no.hal.scxml.scxmlxt.ScxmlxtPackage;
import no.hal.scxml.scxmlxt.State;
import no.hal.scxml.scxmlxt.StateMachine;
import no.hal.scxml.scxmlxt.StringLiteral;
import no.hal.scxml.scxmlxt.SymbolicAction;
import no.hal.scxml.scxmlxt.SymbolicEvent;
import no.hal.scxml.scxmlxt.TimerEvent;
import no.hal.scxml.scxmlxt.Transition;
import no.hal.scxml.scxmlxt.VarDef;
import org.apache.commons.scxml.io.SCXMLSerializer;
import org.apache.commons.scxml.model.Assign;
import org.apache.commons.scxml.model.Executable;
import org.apache.commons.scxml.model.Initial;
import org.apache.commons.scxml.model.OnEntry;
import org.apache.commons.scxml.model.OnExit;
import org.apache.commons.scxml.model.Parallel;
import org.apache.commons.scxml.model.SCXML;
import org.apache.commons.scxml.model.Send;
import org.apache.commons.scxml.model.TransitionTarget;
import org.apache.commons.scxml.model.Var;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;
public class ScxmlGenerator {
private ScxmlFactory scxmlFactory = new ScxmlFactory();
private Map<Object, Object> objectMap = new HashMap<Object, Object>();
private <T> T getScxmlObject(Object object, Class<T> c) {
return getScxmlObject(object, c, false);
}
private <T> T getScxmlObject(Object object, Class<T> c, boolean create) {
T scxmlObject = (object != null ? (T)objectMap.get(object) : null);
if (scxmlObject == null && create) {
scxmlObject = scxmlFactory.create(c);
objectMap.put(object, scxmlObject);
if (object != null) {
objectMap.put(scxmlObject, object);
}
}
return scxmlObject;
}
public Object getScxmlxtObject(Object scxmlObject) {
return objectMap.get(scxmlObject);
}
private Map<String, TransitionTarget> stateIdMap = new HashMap<String, TransitionTarget>();
private void setStateId(TransitionTarget state, String stateId) {
stateIdMap.put(stateId, state);
state.setId(stateId);
}
public TransitionTarget getState(String stateId) {
return stateIdMap.get(stateId);
}
private SCXML scxml;
private List<Transition> unresolvedTransitions = new ArrayList<Transition>();
private Map<String, Object> rootVariables = new HashMap<String, Object>();
public Map<String, Object> getRootVariables() {
return rootVariables;
}
public SCXML generateScxml(StateMachine stateMachine) {
scxml = new SCXML();
unresolvedTransitions.clear();
TransitionTarget rootState = expandState(stateMachine);
scxml.addChild(rootState);
for (Transition transition: unresolvedTransitions) {
org.apache.commons.scxml.model.Transition scxmlTransition = getScxmlObject(transition, org.apache.commons.scxml.model.Transition.class);
TransitionTarget target = getScxmlObject(transition.getTarget(), TransitionTarget.class);
scxmlTransition.setNext(target.getId());
scxml.addTarget(target);
}
scxml.addTarget(rootState);
scxml.setInitial(rootState.getId());
return scxml;
}
private TransitionTarget expandState(AbstractState state) {
List<State> states = state.getStates();
List<State> initialStates = new ArrayList<State>();
for (State substate: states) {
if (substate.getInitialTransition() != null) {
initialStates.add(substate);
}
}
TransitionTarget transitionTarget = null;
String id = computeStateId(state);
if (initialStates.size() == 1) {
transitionTarget = expandRegionStates(initialStates.get(0), new org.apache.commons.scxml.model.State());
} else if (states.size() == 1) {
transitionTarget = expandRegionStates(states.get(0), new org.apache.commons.scxml.model.State());
} else if (states.size() > 0) {
Parallel parallel = getScxmlObject(state, Parallel.class, true);
int count = 0;
for (State initialState: (initialStates.size() > 1 ? initialStates : states)) {
TransitionTarget region = expandRegionStates(initialState, null);
if (region.getId() == null) {
setStateId(region, id + "." + count++);
}
parallel.addChild(region);
}
transitionTarget = parallel;
} else {
transitionTarget = getScxmlObject(state, org.apache.commons.scxml.model.State.class, true);
}
setStateId(transitionTarget, id);
for (VarDef varDef : state.getVariables()) {
expandVariable(varDef, transitionTarget);
}
for (AbstractTransition transition: state.getTransitions()) {
expandTransition(transition, transition.getEvent(), state, transitionTarget);
}
return transitionTarget;
}
private String computeStateId(AbstractState state) {
StringBuilder buffer = new StringBuilder();
EObject eObject = state;
while (eObject != null) {
if (eObject instanceof State) {
if (buffer.length() > 0) {
buffer.insert(0, '.');
}
buffer.insert(0, ((State)eObject).getName());
}
eObject = eObject.eContainer();
}
return (buffer.length() > 0 ? buffer.toString() : "scxml:root");
}
private void setTransitionTarget(org.apache.commons.scxml.model.Transition transition, TransitionTarget transitionTarget) {
transition.setNext(transitionTarget.getId());
scxml.addTarget(transitionTarget);
}
private Initial createInitialScxmlTransition(TransitionTarget initialState) {
Initial initial = new Initial();
org.apache.commons.scxml.model.Transition scxmlTransition = new org.apache.commons.scxml.model.Transition();
initial.setTransition(scxmlTransition);
setTransitionTarget(scxmlTransition, initialState);
return initial;
}
private TransitionTarget expandRegionStates(State initialState, org.apache.commons.scxml.model.State region) {
List<AbstractState> regionStates = computeStateClosure(initialState);
for (AbstractState regionState : regionStates) {
TransitionTarget substate = expandState(regionState);
if (region == null) {
region = new org.apache.commons.scxml.model.State();
}
if (regionState == initialState) {
region.setInitial(expandInitialTransition(initialState, substate));
}
region.addChild(substate);
}
return region;
}
private Initial expandInitialTransition(State initialState,
TransitionTarget substate) {
Initial initial = createInitialScxmlTransition(substate);
org.apache.commons.scxml.model.Transition scxmlTransition = initial.getTransition();
scxmlTransition.setEvent(expandEvent(null));
scxmlTransition.setCond(expandCondition(null));
InitialTransition initialTransition = initialState.getInitialTransition();
if (initialTransition.getAction() != null) {
expandAction(initialTransition.getAction(), scxmlTransition);
}
setTransitionTarget(scxmlTransition, substate);
return initial;
}
private void expandTransition(AbstractTransition transition, Event event, AbstractState parent, TransitionTarget scxmlState) {
if (event instanceof TimerEvent) {
expandTimerEventTransition(transition, (TimerEvent)event, parent, scxmlState);
} else if (transition instanceof InternalTransition &&
event instanceof AbstractTransitionEvent &&
transition.getCondition() == null) {
AbstractTransitionEvent transitionEvent = (AbstractTransitionEvent)event;
if (transitionEvent instanceof EnterEvent || (transitionEvent.getSource() == null && transitionEvent.getTarget() == parent)) {
OnEntry onEntry = new OnEntry();
expandAction(transition.getAction(), onEntry);
scxmlState.setOnEntry(onEntry);
} else if (transitionEvent instanceof ExitEvent || (transitionEvent.getSource() == parent && transitionEvent.getTarget() == null)) {
OnExit onExit = new OnExit();
expandAction(transition.getAction(), onExit);
scxmlState.setOnExit(onExit);
} else {
expandTransition(transition, event, scxmlState);
}
} else {
expandTransition(transition, event, scxmlState);
}
}
private void expandTimerEventTransition(AbstractTransition transition, TimerEvent timerEvent, AbstractState parent, TransitionTarget scxmlState) {
InternalTransition timerTransition = ScxmlxtFactory.eINSTANCE.createInternalTransition();
SymbolicAction timerAction = ScxmlxtFactory.eINSTANCE.createSymbolicAction();
String timerEventName = ScxmlxtGenerator.getFullName(parent, null) + "#" + parent.getTransitions().size();
timerAction.setName(timerEventName);
timerAction.setDelay(timerEvent.getDelay());
timerTransition.setAction(timerAction);
// note that timerTransition is not actually contained in parent
expandTransition(timerTransition, timerEvent.getEvent(), parent, scxmlState);
SymbolicEvent timerEventReplacement = ScxmlxtFactory.eINSTANCE.createSymbolicEvent();
timerEventReplacement.setName(timerEventName);
expandTransition(transition, timerEventReplacement, parent, scxmlState);
}
private void expandTransition(AbstractTransition transition, Event event, TransitionTarget scxmlState) {
org.apache.commons.scxml.model.Transition scxmlTransition = getScxmlObject(transition, org.apache.commons.scxml.model.Transition.class, true);
scxmlState.addTransition(scxmlTransition);
scxmlTransition.setEvent(expandEvent(event));
scxmlTransition.setCond(expandCondition(transition.getCondition()));
if (transition instanceof Transition) {
unresolvedTransitions.add((Transition)transition);
}
if (transition.getAction() != null) {
expandAction(transition.getAction(), scxmlTransition);
}
}
private List<ScriptEventHandler> scriptEventHandlers = new ArrayList<ScriptEventHandler>();
public ScriptEventHandler[] getScriptEventHandlers() {
return scriptEventHandlers.toArray(new ScriptEventHandler[scriptEventHandlers.size()]);
}
private final static String ENTER_EVENT_QUALIFIER = ".entry", EXIT_EVENT_QUALIFIER = ".exit";
private String expandEvent(Event event) {
if (event instanceof SymbolicEvent) {
return ((SymbolicEvent)event).getName();
} else if (event instanceof AbstractTransitionEvent) {
AbstractTransitionEvent transitionEvent = (AbstractTransitionEvent)event;
if (transitionEvent.getSource() != null && transitionEvent.getTarget() == null) {
return computeStateId(transitionEvent.getSource()) + EXIT_EVENT_QUALIFIER;
} else if (transitionEvent.getSource() == null && transitionEvent.getTarget() != null) {
return computeStateId(transitionEvent.getTarget()) + ENTER_EVENT_QUALIFIER;
} else {
// TODO: currently unsupported
}
} else if (event instanceof ScriptEvent) {
State state = (State)((AbstractTransition)event.eContainer()).eContainer();
org.apache.commons.scxml.model.State scxmlState = getScxmlObject(state, org.apache.commons.scxml.model.State.class);
ScriptEventHandler scriptEventHandler = new ScriptEventHandler(scxmlState, ((ScriptEvent)event).getScript());
scriptEventHandlers.add(scriptEventHandler);
return scriptEventHandler.getScriptEventId();
}
return null;
}
private void expandAction(Action action, Executable exec) {
org.apache.commons.scxml.model.Action scxmlAction = null;
if (action instanceof SymbolicAction) {
SymbolicAction symbolicAction = (SymbolicAction)action;
Send sendAction = getScxmlObject(action, Send.class, true);
sendAction.setEvent("'" + symbolicAction.getName() + "'");
if (symbolicAction.getDelay() != null) {
Object delay = expandExpression(symbolicAction.getDelay(), true);
sendAction.setDelay(delay != null ? String.valueOf(delay) : null);
}
sendAction.setTargettype("'scxml'");
scxmlAction = sendAction;
} else if (action instanceof AssignmentAction) {
Assign assignAction = getScxmlObject(action, Assign.class, true);
AssignmentAction assignmentAction = (AssignmentAction)action;
assignAction.setName(assignmentAction.getVar().getName());
Object value = expandExpression(assignmentAction.getValue(), false);
assignAction.setExpr(value != null ? String.valueOf(value) : null);
scxmlAction = assignAction;
} else if (action instanceof ScriptAction) {
no.hal.scxml.javascript.ScriptAction scriptAction = getScxmlObject(action, no.hal.scxml.javascript.ScriptAction.class, true);
scriptAction.setScript(((ScriptAction)action).getScript());
scxmlAction = scriptAction;
// TODO
}
if (scxmlAction != null) {
exec.addAction(scxmlAction);
scxmlAction.setParent(exec);
}
}
private String expandCondition(Condition condition) {
if (condition instanceof Condition) {
return condition.getScript();
}
return null;
}
private void expandVariable(VarDef var, TransitionTarget scxmlState) {
Var scxmlVar = getScxmlObject(var, Var.class, true);
scxmlVar.setName(var.getName());
Object init = expandExpression(var.getInit(), false);
scxmlVar.setExpr(init != null ? String.valueOf(init) : null);
OnEntry onEntry = getOnEntry(scxmlState);
onEntry.addAction(scxmlVar);
scxmlVar.setParent(onEntry);
}
public OnEntry getOnEntry(TransitionTarget scxmlState) {
OnEntry onEntry = scxmlState.getOnEntry();
if (onEntry == null) {
onEntry = new OnEntry();
scxmlState.setOnEntry(onEntry);
}
return onEntry;
}
private Object expandExpression(Expression expr, boolean quoteConstant) {
if (expr instanceof BooleanLiteral) {
return ((BooleanLiteral)expr).isBooleanValue();
} else if (expr instanceof DelayLiteral) {
DelayLiteral delayLiteral = (DelayLiteral)expr;
return "'" + delayLiteral.getIntValue() + delayLiteral.getTimeUnit() + "'";
} else if (expr instanceof IntLiteral) {
int intValue = ((IntLiteral)expr).getIntValue();
return (quoteConstant ? "'" + intValue + "'" : intValue);
} else if (expr instanceof FloatLiteral) {
double floatValue = ((FloatLiteral)expr).getFloatValue();
return (quoteConstant ? "'" + floatValue + "'" : floatValue);
} else if (expr instanceof StringLiteral) {
String stringValue = ((StringLiteral)expr).getStringValue();
return "'" + stringValue + "'";
} else if (expr instanceof EObjectUriLiteral) {
EObjectUriLiteral eObjectUriLiteral = (EObjectUriLiteral)expr;
return expandUriReference(eObjectUriLiteral.getResourceUri(), eObjectUriLiteral.getUriFragment());
} else if (expr instanceof EObjectReference) {
URI uri = EcoreUtil.getURI(((EObjectReference)expr).getEObject());
return expandUriReference(uri.trimFragment(), uri.fragment());
} else if (expr instanceof ResourceUriLiteral) {
ResourceUriLiteral resourceUriLiteral = (ResourceUriLiteral)expr;
return expandUriReference(resourceUriLiteral.getResourceUri(), null);
} else if (expr instanceof AbstractUriLiteral) {
return "org.eclipse.emf.common.util.URI.createURI(\"" + ((AbstractUriLiteral)expr).getUri() + "\")";
} else if (expr instanceof ScriptExpression) {
return ((ScriptExpression)expr).getScript();
}
return null;
}
private String expandUriReference(URI resourceUri, String uriFragment) {
for (Map.Entry<String, Object> entry: rootVariables.entrySet()) {
if (entry.getValue() instanceof EObject && uriFragment != null) {
URI uri = EcoreUtil.getURI((EObject)entry.getValue());
if (uri.equals(resourceUri)) {
return (uriFragment != null ? entry.getKey() + ".getEObject(\"" + uriFragment + "\")" : entry.getKey());
}
}
if (entry.getValue() instanceof Resource) {
URI uri = ((Resource)entry.getValue()).getURI();
if (uri.equals(resourceUri)) {
return (uriFragment != null ? entry.getKey() + ".getEObject(\"" + uriFragment + "\")" : entry.getKey());
}
}
}
return (uriFragment != null ? "getEObject(\"" + resourceUri.appendFragment(uriFragment) + "\")" : "getResource(\"" + resourceUri + "\")");
}
private List<AbstractState> computeStateClosure(State state) {
List<AbstractState> closure = new ArrayList<AbstractState>();
computeStateClosure(state, (AbstractState)state.eContainer(), closure, new ArrayList<AbstractState>());
return closure;
}
private void computeStateClosure(AbstractState state, AbstractState parent, List<AbstractState> closure, List<AbstractState> visited) {
if (! visited.contains(state)) {
visited.add(state);
if (state.eContainer() == parent) {
closure.add(state);
}
for (AbstractTransition t : state.getTransitions()) {
if (t instanceof Transition) {
AbstractState targetParent = ((Transition)t).getTarget();
while (targetParent != null && targetParent.eContainer() != parent) {
targetParent = (AbstractState)targetParent.eContainer();
}
if (targetParent != null) {
computeStateClosure(targetParent, parent, closure, visited);
}
}
}
for (State substate: state.getStates()) {
computeStateClosure(substate, parent, closure, visited);
}
}
}
public static void main(String[] args) {
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xml", new XMLResourceFactoryImpl());
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("ecore", new EcoreResourceFactoryImpl());
EPackage.Registry.INSTANCE.put(ScxmlxtPackage.eNS_URI, ScxmlxtPackage.eINSTANCE);
URI baseUri = URI.createURI(String.valueOf(ScxmlxtGenerator.class.getResource("TestGame.ecore")));
URI model = URI.createURI("Game1.xmi").resolve(baseUri);
ScxmlxtGenerator scxmlxtGenerator = new ScxmlxtGenerator(new ResourceSetImpl().getResource(model, true).getContents().get(0), baseUri);
StateMachine stateMachine = scxmlxtGenerator.generateScxmlxt();
ScxmlGenerator scxmlGenerator = new ScxmlGenerator();
scxmlGenerator.getRootVariables().putAll(scxmlxtGenerator.getRootVariables());
SCXML scxml = scxmlGenerator.generateScxml(stateMachine);
System.out.println(SCXMLSerializer.serialize(scxml));
}
}