You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/11/03 15:49:48 UTC
(commons-scxml) 02/03: Sort main members
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-scxml.git
commit da2159fffd8489d2fb25e925aa5893639f86b23c
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Nov 3 11:49:33 2023 -0400
Sort main members
---
.../commons/scxml2/ActionExecutionContext.java | 38 +-
.../java/org/apache/commons/scxml2/Context.java | 62 +-
.../java/org/apache/commons/scxml2/Evaluator.java | 46 +-
.../apache/commons/scxml2/EvaluatorFactory.java | 56 +-
.../apache/commons/scxml2/EvaluatorProvider.java | 10 +-
.../org/apache/commons/scxml2/EventBuilder.java | 50 +-
.../org/apache/commons/scxml2/EventDispatcher.java | 14 +-
.../commons/scxml2/NotificationRegistry.java | 36 +-
.../commons/scxml2/ParentSCXMLIOProcessor.java | 8 +-
.../org/apache/commons/scxml2/PathResolver.java | 12 +-
.../java/org/apache/commons/scxml2/SCInstance.java | 452 +-
.../scxml2/SCInstanceObjectInputStream.java | 16 +-
.../commons/scxml2/SCXMLExecutionContext.java | 426 +-
.../org/apache/commons/scxml2/SCXMLExecutor.java | 482 +--
.../commons/scxml2/SCXMLExpressionException.java | 16 +-
.../org/apache/commons/scxml2/SCXMLSemantics.java | 110 +-
.../apache/commons/scxml2/SCXMLSystemContext.java | 96 +-
.../apache/commons/scxml2/StateConfiguration.java | 38 +-
.../java/org/apache/commons/scxml2/Status.java | 18 +-
.../org/apache/commons/scxml2/TriggerEvent.java | 114 +-
.../commons/scxml2/env/AbstractBaseEvaluator.java | 38 +-
.../commons/scxml2/env/AbstractStateMachine.java | 208 +-
.../commons/scxml2/env/EffectiveContextMap.java | 34 +-
.../org/apache/commons/scxml2/env/LogUtils.java | 34 +-
.../apache/commons/scxml2/env/SimpleContext.java | 108 +-
.../commons/scxml2/env/SimpleDispatcher.java | 54 +-
.../commons/scxml2/env/SimpleErrorReporter.java | 44 +-
.../java/org/apache/commons/scxml2/env/Tracer.java | 32 +-
.../org/apache/commons/scxml2/env/URLResolver.java | 14 +-
.../commons/scxml2/env/groovy/GroovyContext.java | 92 +-
.../scxml2/env/groovy/GroovyContextBinding.java | 30 +-
.../commons/scxml2/env/groovy/GroovyEvaluator.java | 116 +-
.../env/groovy/GroovyExtendableScriptCache.java | 268 +-
.../scxml2/env/groovy/GroovySCXMLScript.java | 60 +-
.../commons/scxml2/env/javascript/JSBindings.java | 66 +-
.../commons/scxml2/env/javascript/JSContext.java | 16 +-
.../commons/scxml2/env/javascript/JSEvaluator.java | 242 +-
.../commons/scxml2/env/jexl/JexlContext.java | 18 +-
.../commons/scxml2/env/jexl/JexlEvaluator.java | 96 +-
.../commons/scxml2/env/minimal/MinimalContext.java | 14 +-
.../scxml2/env/minimal/MinimalEvaluator.java | 44 +-
.../org/apache/commons/scxml2/invoke/Invoker.java | 44 +-
.../commons/scxml2/invoke/InvokerException.java | 16 +-
.../commons/scxml2/invoke/SimpleSCXMLInvoker.java | 68 +-
.../apache/commons/scxml2/io/ContentParser.java | 186 +-
.../org/apache/commons/scxml2/io/ModelUpdater.java | 318 +-
.../org/apache/commons/scxml2/io/SCXMLReader.java | 4314 ++++++++++----------
.../org/apache/commons/scxml2/io/SCXMLWriter.java | 1452 +++----
.../org/apache/commons/scxml2/model/Action.java | 38 +-
.../commons/scxml2/model/ActionExecutionError.java | 8 +-
.../commons/scxml2/model/ActionsContainer.java | 14 +-
.../org/apache/commons/scxml2/model/Assign.java | 92 +-
.../org/apache/commons/scxml2/model/Cancel.java | 72 +-
.../org/apache/commons/scxml2/model/Content.java | 18 +-
.../commons/scxml2/model/CustomActionWrapper.java | 78 +-
.../java/org/apache/commons/scxml2/model/Data.java | 54 +-
.../org/apache/commons/scxml2/model/Datamodel.java | 18 +-
.../org/apache/commons/scxml2/model/DoneData.java | 16 +-
.../org/apache/commons/scxml2/model/ElseIf.java | 16 +-
.../commons/scxml2/model/EnterableState.java | 52 +-
.../apache/commons/scxml2/model/Executable.java | 18 +-
.../org/apache/commons/scxml2/model/Final.java | 32 +-
.../org/apache/commons/scxml2/model/Foreach.java | 58 +-
.../org/apache/commons/scxml2/model/History.java | 52 +-
.../java/org/apache/commons/scxml2/model/If.java | 56 +-
.../org/apache/commons/scxml2/model/Initial.java | 44 +-
.../org/apache/commons/scxml2/model/Invoke.java | 432 +-
.../org/apache/commons/scxml2/model/JsonValue.java | 10 +-
.../java/org/apache/commons/scxml2/model/Log.java | 36 +-
.../commons/scxml2/model/ModelException.java | 14 +-
.../org/apache/commons/scxml2/model/OnEntry.java | 20 +-
.../org/apache/commons/scxml2/model/OnExit.java | 20 +-
.../org/apache/commons/scxml2/model/Parallel.java | 14 +-
.../org/apache/commons/scxml2/model/Param.java | 48 +-
.../commons/scxml2/model/PayloadBuilder.java | 122 +-
.../org/apache/commons/scxml2/model/Raise.java | 28 +-
.../org/apache/commons/scxml2/model/SCXML.java | 294 +-
.../org/apache/commons/scxml2/model/Script.java | 42 +-
.../java/org/apache/commons/scxml2/model/Send.java | 474 +--
.../commons/scxml2/model/SimpleTransition.java | 164 +-
.../org/apache/commons/scxml2/model/State.java | 86 +-
.../org/apache/commons/scxml2/model/TextValue.java | 10 +-
.../apache/commons/scxml2/model/Transition.java | 74 +-
.../commons/scxml2/model/TransitionTarget.java | 106 +-
.../commons/scxml2/model/TransitionalState.java | 198 +-
.../java/org/apache/commons/scxml2/model/Var.java | 52 +-
.../scxml2/semantics/SCXMLSemanticsImpl.java | 1426 +++----
.../org/apache/commons/scxml2/semantics/Step.java | 26 +-
.../commons/scxml2/system/EventVariable.java | 20 +-
89 files changed, 7289 insertions(+), 7289 deletions(-)
diff --git a/src/main/java/org/apache/commons/scxml2/ActionExecutionContext.java b/src/main/java/org/apache/commons/scxml2/ActionExecutionContext.java
index b402c751..1cd16840 100644
--- a/src/main/java/org/apache/commons/scxml2/ActionExecutionContext.java
+++ b/src/main/java/org/apache/commons/scxml2/ActionExecutionContext.java
@@ -40,17 +40,10 @@ public class ActionExecutionContext {
}
/**
- * @return Returns the state machine
- */
- public SCXML getStateMachine() {
- return exctx.getStateMachine();
- }
-
- /**
- * @return Returns the global context
+ * @return Returns the SCXML Execution Logger for the application
*/
- public Context getGlobalContext() {
- return exctx.getScInstance().getGlobalContext();
+ public Log getAppLog() {
+ return exctx.getAppLog();
}
/**
@@ -62,17 +55,17 @@ public class ActionExecutionContext {
}
/**
- * @return Returns The evaluator.
+ * @return Returns the error reporter
*/
- public Evaluator getEvaluator() {
- return exctx.getEvaluator();
+ public ErrorReporter getErrorReporter() {
+ return exctx.getErrorReporter();
}
/**
- * @return Returns the error reporter
+ * @return Returns The evaluator.
*/
- public ErrorReporter getErrorReporter() {
- return exctx.getErrorReporter();
+ public Evaluator getEvaluator() {
+ return exctx.getEvaluator();
}
/**
@@ -82,6 +75,13 @@ public class ActionExecutionContext {
return exctx.getEventDispatcher();
}
+ /**
+ * @return Returns the global context
+ */
+ public Context getGlobalContext() {
+ return exctx.getScInstance().getGlobalContext();
+ }
+
/**
* @return Returns the I/O Processor for the internal event queue
*/
@@ -90,9 +90,9 @@ public class ActionExecutionContext {
}
/**
- * @return Returns the SCXML Execution Logger for the application
+ * @return Returns the state machine
*/
- public Log getAppLog() {
- return exctx.getAppLog();
+ public SCXML getStateMachine() {
+ return exctx.getStateMachine();
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/scxml2/Context.java b/src/main/java/org/apache/commons/scxml2/Context.java
index 341df93b..ebde03c8 100644
--- a/src/main/java/org/apache/commons/scxml2/Context.java
+++ b/src/main/java/org/apache/commons/scxml2/Context.java
@@ -25,32 +25,35 @@ import java.util.Map;
public interface Context {
/**
- * Assigns a new value to an existing variable or creates a new one.
- * The method searches the chain of parent Contexts for variable
- * existence.
+ * Gets the value of this variable; delegating to parent.
*
- * @param name The variable name
- * @param value The variable value
+ * @param name The name of the variable
+ * @return The value (or null)
*/
- void set(String name, Object value);
+ Object get(String name);
/**
- * Assigns a new value to an existing variable or creates a new one.
- * The method allows to shaddow a variable of the same name up the
- * Context chain.
+ * Gets the parent Context, may be null.
*
- * @param name The variable name
- * @param value The variable value
+ * @return The parent Context in a chained Context environment
*/
- void setLocal(String name, Object value);
+ Context getParent();
/**
- * Gets the value of this variable; delegating to parent.
+ * Gets the SCXMLSystemContext for this Context, should not be null unless this is the root Context
*
- * @param name The name of the variable
- * @return The value (or null)
+ * @return The SCXMLSystemContext in a chained Context environment
*/
- Object get(String name);
+ SCXMLSystemContext getSystemContext();
+
+ /**
+ * Gets the Map of all variables in this Context.
+ *
+ * @return Local variable entries Map
+ * To get variables in parent Context, call getParent().getVars().
+ * @see #getParent()
+ */
+ Map<String, Object> getVars();
/**
* Check if this variable exists, delegating to parent.
@@ -68,32 +71,29 @@ public interface Context {
*/
boolean hasLocal(String name);
- /**
- * Gets the Map of all variables in this Context.
- *
- * @return Local variable entries Map
- * To get variables in parent Context, call getParent().getVars().
- * @see #getParent()
- */
- Map<String, Object> getVars();
-
/**
* Clear this Context.
*/
void reset();
/**
- * Gets the parent Context, may be null.
+ * Assigns a new value to an existing variable or creates a new one.
+ * The method searches the chain of parent Contexts for variable
+ * existence.
*
- * @return The parent Context in a chained Context environment
+ * @param name The variable name
+ * @param value The variable value
*/
- Context getParent();
+ void set(String name, Object value);
/**
- * Gets the SCXMLSystemContext for this Context, should not be null unless this is the root Context
+ * Assigns a new value to an existing variable or creates a new one.
+ * The method allows to shaddow a variable of the same name up the
+ * Context chain.
*
- * @return The SCXMLSystemContext in a chained Context environment
+ * @param name The variable name
+ * @param value The variable value
*/
- SCXMLSystemContext getSystemContext();
+ void setLocal(String name, Object value);
}
diff --git a/src/main/java/org/apache/commons/scxml2/Evaluator.java b/src/main/java/org/apache/commons/scxml2/Evaluator.java
index f72cc005..357a2821 100644
--- a/src/main/java/org/apache/commons/scxml2/Evaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/Evaluator.java
@@ -31,18 +31,6 @@ public interface Evaluator {
/** Default Data Model name **/
String DEFAULT_DATA_MODEL = "";
- /**
- * Gets the datamodel type supported by this Evaluator
- * @return The supported datamodel type
- */
- String getSupportedDatamodel();
-
- /**
- * If this Evaluator only supports a global context.
- * @return true if this Evaluator only support a global context
- */
- boolean requiresGlobalContext();
-
/**
* @param data data to be cloned
* @return A deep clone of the data
@@ -60,6 +48,17 @@ public interface Evaluator {
Object eval(Context ctx, String expr)
throws SCXMLExpressionException;
+ /**
+ * Assigns data to a location
+ *
+ * @param ctx variable context
+ * @param location location expression
+ * @param data the data to assign.
+ * @throws SCXMLExpressionException A malformed expression exception
+ */
+ void evalAssign(Context ctx, String location, Object data)
+ throws SCXMLExpressionException;
+
/**
* Evaluate a condition.
* Manifests as "cond" attributes of <transition>,
@@ -73,17 +72,6 @@ public interface Evaluator {
Boolean evalCond(Context ctx, String expr)
throws SCXMLExpressionException;
- /**
- * Assigns data to a location
- *
- * @param ctx variable context
- * @param location location expression
- * @param data the data to assign.
- * @throws SCXMLExpressionException A malformed expression exception
- */
- void evalAssign(Context ctx, String location, Object data)
- throws SCXMLExpressionException;
-
/**
* Evaluate a script.
* Manifests as <script> element.
@@ -96,6 +84,12 @@ public interface Evaluator {
Object evalScript(Context ctx, String script)
throws SCXMLExpressionException;
+ /**
+ * Gets the datamodel type supported by this Evaluator
+ * @return The supported datamodel type
+ */
+ String getSupportedDatamodel();
+
/**
* Create a new child context.
*
@@ -104,5 +98,11 @@ public interface Evaluator {
*/
Context newContext(Context parent);
+ /**
+ * If this Evaluator only supports a global context.
+ * @return true if this Evaluator only support a global context
+ */
+ boolean requiresGlobalContext();
+
}
diff --git a/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java b/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java
index 3886f954..daa77fd3 100644
--- a/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java
+++ b/src/main/java/org/apache/commons/scxml2/EvaluatorFactory.java
@@ -56,25 +56,27 @@ public class EvaluatorFactory {
private static final EvaluatorFactory INSTANCE = new EvaluatorFactory();
- private final Map<String, EvaluatorProvider> providers = new ConcurrentHashMap<>();
-
- private EvaluatorFactory() {
- providers.put(JSEvaluator.SUPPORTED_DATA_MODEL, new JSEvaluator.JSEvaluatorProvider());
- providers.put(GroovyEvaluator.SUPPORTED_DATA_MODEL, new GroovyEvaluator.GroovyEvaluatorProvider());
- providers.put(JexlEvaluator.SUPPORTED_DATA_MODEL, new JexlEvaluator.JexlEvaluatorProvider());
- providers.put(MinimalEvaluator.SUPPORTED_DATA_MODEL, new MinimalEvaluator.MinimalEvaluatorProvider());
- providers.put(DEFAULT_DATA_MODEL, providers.get(JexlEvaluator.SUPPORTED_DATA_MODEL));
- }
-
- public static void setDefaultProvider(final EvaluatorProvider defaultProvider) {
- INSTANCE.providers.put(DEFAULT_DATA_MODEL, defaultProvider);
- }
-
@SuppressWarnings("unused")
public static EvaluatorProvider getDefaultProvider() {
return INSTANCE.providers.get(DEFAULT_DATA_MODEL);
}
+ /**
+ * Returns a dedicated Evaluator instance for a specific SCXML document its documentmodel.
+ * <p>If no SCXML document is provided a default Evaluator will be returned.</p>
+ * @param document The document to return a dedicated Evaluator for. May be null to retrieve the default Evaluator.
+ * @return a new and not sharable Evaluator instance for the provided document, or a default Evaluator otherwise
+ * @throws ModelException If the SCXML document datamodel is not supported.
+ */
+ public static Evaluator getEvaluator(final SCXML document) throws ModelException {
+ final String datamodelName = document != null ? document.getDatamodelName() : null;
+ final EvaluatorProvider provider = INSTANCE.providers.get(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName);
+ if (provider == null) {
+ throw new ModelException("Unsupported SCXML document datamodel \""+(datamodelName)+"\"");
+ }
+ return document != null ? provider.getEvaluator(document) : provider.getEvaluator();
+ }
+
@SuppressWarnings("unused")
public static EvaluatorProvider getEvaluatorProvider(final String datamodelName) {
return INSTANCE.providers.get(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName);
@@ -85,24 +87,22 @@ public class EvaluatorFactory {
INSTANCE.providers.put(provider.getSupportedDatamodel(), provider);
}
+ public static void setDefaultProvider(final EvaluatorProvider defaultProvider) {
+ INSTANCE.providers.put(DEFAULT_DATA_MODEL, defaultProvider);
+ }
+
@SuppressWarnings("unused")
public static void unregisterEvaluatorProvider(final String datamodelName) {
INSTANCE.providers.remove(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName);
}
- /**
- * Returns a dedicated Evaluator instance for a specific SCXML document its documentmodel.
- * <p>If no SCXML document is provided a default Evaluator will be returned.</p>
- * @param document The document to return a dedicated Evaluator for. May be null to retrieve the default Evaluator.
- * @return a new and not sharable Evaluator instance for the provided document, or a default Evaluator otherwise
- * @throws ModelException If the SCXML document datamodel is not supported.
- */
- public static Evaluator getEvaluator(final SCXML document) throws ModelException {
- final String datamodelName = document != null ? document.getDatamodelName() : null;
- final EvaluatorProvider provider = INSTANCE.providers.get(datamodelName == null ? DEFAULT_DATA_MODEL : datamodelName);
- if (provider == null) {
- throw new ModelException("Unsupported SCXML document datamodel \""+(datamodelName)+"\"");
- }
- return document != null ? provider.getEvaluator(document) : provider.getEvaluator();
+ private final Map<String, EvaluatorProvider> providers = new ConcurrentHashMap<>();
+
+ private EvaluatorFactory() {
+ providers.put(JSEvaluator.SUPPORTED_DATA_MODEL, new JSEvaluator.JSEvaluatorProvider());
+ providers.put(GroovyEvaluator.SUPPORTED_DATA_MODEL, new GroovyEvaluator.GroovyEvaluatorProvider());
+ providers.put(JexlEvaluator.SUPPORTED_DATA_MODEL, new JexlEvaluator.JexlEvaluatorProvider());
+ providers.put(MinimalEvaluator.SUPPORTED_DATA_MODEL, new MinimalEvaluator.MinimalEvaluatorProvider());
+ providers.put(DEFAULT_DATA_MODEL, providers.get(JexlEvaluator.SUPPORTED_DATA_MODEL));
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/EvaluatorProvider.java b/src/main/java/org/apache/commons/scxml2/EvaluatorProvider.java
index b7b7e677..bf32b2ff 100644
--- a/src/main/java/org/apache/commons/scxml2/EvaluatorProvider.java
+++ b/src/main/java/org/apache/commons/scxml2/EvaluatorProvider.java
@@ -23,11 +23,6 @@ import org.apache.commons.scxml2.model.SCXML;
*/
public interface EvaluatorProvider {
- /**
- * @return The SCXML datamodel type this provider supports
- */
- String getSupportedDatamodel();
-
/**
* @return a default or generic {@link Evaluator} supporting the {@link #getSupportedDatamodel()}
*/
@@ -43,4 +38,9 @@ public interface EvaluatorProvider {
* @return a new and not sharable Evaluator instance
*/
Evaluator getEvaluator(SCXML document);
+
+ /**
+ * @return The SCXML datamodel type this provider supports
+ */
+ String getSupportedDatamodel();
}
diff --git a/src/main/java/org/apache/commons/scxml2/EventBuilder.java b/src/main/java/org/apache/commons/scxml2/EventBuilder.java
index d43ec49e..ee97c242 100644
--- a/src/main/java/org/apache/commons/scxml2/EventBuilder.java
+++ b/src/main/java/org/apache/commons/scxml2/EventBuilder.java
@@ -31,43 +31,41 @@ public class EventBuilder {
this.type = type;
}
- public String getName() {
- return name;
+ public TriggerEvent build() {
+ return new TriggerEvent(name, type, sendId, origin, originType, invokeId, data);
}
- public int getType() {
- return type;
+ public EventBuilder data(final Object data) {
+ this.data = data;
+ return this;
}
- public String getSendId() {
- return sendId;
+ public Object getData() {
+ return data;
}
- public EventBuilder sendId(final String sendId) {
- this.sendId = sendId;
- return this;
+ public String getInvokeId() {
+ return invokeId;
}
- public String getOrigin() {
- return origin;
+ public String getName() {
+ return name;
}
- public EventBuilder origin(final String origin) {
- this.origin = origin;
- return this;
+ public String getOrigin() {
+ return origin;
}
public String getOriginType() {
return originType;
}
- public EventBuilder originType(final String originType) {
- this.originType = originType;
- return this;
+ public String getSendId() {
+ return sendId;
}
- public String getInvokeId() {
- return invokeId;
+ public int getType() {
+ return type;
}
public EventBuilder invokeId(final String invokeId) {
@@ -75,16 +73,18 @@ public class EventBuilder {
return this;
}
- public Object getData() {
- return data;
+ public EventBuilder origin(final String origin) {
+ this.origin = origin;
+ return this;
}
- public EventBuilder data(final Object data) {
- this.data = data;
+ public EventBuilder originType(final String originType) {
+ this.originType = originType;
return this;
}
- public TriggerEvent build() {
- return new TriggerEvent(name, type, sendId, origin, originType, invokeId, data);
+ public EventBuilder sendId(final String sendId) {
+ this.sendId = sendId;
+ return this;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/EventDispatcher.java b/src/main/java/org/apache/commons/scxml2/EventDispatcher.java
index dfe0e1f0..6a87d068 100644
--- a/src/main/java/org/apache/commons/scxml2/EventDispatcher.java
+++ b/src/main/java/org/apache/commons/scxml2/EventDispatcher.java
@@ -28,6 +28,13 @@ import org.apache.commons.scxml2.invoke.Invoker;
*/
public interface EventDispatcher {
+ /**
+ * Cancel the specified send message.
+ *
+ * @param sendId The ID of the send message to cancel
+ */
+ void cancel(String sendId);
+
/**
* A EventDispatcher keeps track of outstanding (pending) events to be send on behalf of the statemachine
* it is 'attached' to.
@@ -38,13 +45,6 @@ public interface EventDispatcher {
*/
EventDispatcher newInstance();
- /**
- * Cancel the specified send message.
- *
- * @param sendId The ID of the send message to cancel
- */
- void cancel(String sendId);
-
/**
* Send this message to the target.
*
diff --git a/src/main/java/org/apache/commons/scxml2/NotificationRegistry.java b/src/main/java/org/apache/commons/scxml2/NotificationRegistry.java
index ff052fe4..5f20fe4f 100644
--- a/src/main/java/org/apache/commons/scxml2/NotificationRegistry.java
+++ b/src/main/java/org/apache/commons/scxml2/NotificationRegistry.java
@@ -61,24 +61,6 @@ public final class NotificationRegistry {
}
}
- /**
- * Deregister this SCXMLListener for this Observable.
- *
- * @param source The observable this listener wants to stop listening to
- * @param lst The listener
- */
- synchronized void removeListener(final Observable source, final SCXMLListener lst) {
- if (source != null && source.getObservableId() != null) {
- final Set<SCXMLListener> entries = regs.get(source.getObservableId());
- if (entries != null) {
- entries.remove(lst);
- if (entries.isEmpty()) {
- regs.remove(source.getObservableId());
- }
- }
- }
- }
-
/**
* Inform all relevant listeners that a EnterableState has been
* entered.
@@ -138,5 +120,23 @@ public final class NotificationRegistry {
}
}
}
+
+ /**
+ * Deregister this SCXMLListener for this Observable.
+ *
+ * @param source The observable this listener wants to stop listening to
+ * @param lst The listener
+ */
+ synchronized void removeListener(final Observable source, final SCXMLListener lst) {
+ if (source != null && source.getObservableId() != null) {
+ final Set<SCXMLListener> entries = regs.get(source.getObservableId());
+ if (entries != null) {
+ entries.remove(lst);
+ if (entries.isEmpty()) {
+ regs.remove(source.getObservableId());
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/scxml2/ParentSCXMLIOProcessor.java b/src/main/java/org/apache/commons/scxml2/ParentSCXMLIOProcessor.java
index f3cc08c6..f0a6dffc 100644
--- a/src/main/java/org/apache/commons/scxml2/ParentSCXMLIOProcessor.java
+++ b/src/main/java/org/apache/commons/scxml2/ParentSCXMLIOProcessor.java
@@ -33,14 +33,14 @@ public class ParentSCXMLIOProcessor implements SCXMLIOProcessor {
}
}
- public String getInvokeId() {
- return invokeId;
- }
-
public synchronized void close() {
processor = null;
}
+ public String getInvokeId() {
+ return invokeId;
+ }
+
public synchronized boolean isClosed() {
return processor == null;
}
diff --git a/src/main/java/org/apache/commons/scxml2/PathResolver.java b/src/main/java/org/apache/commons/scxml2/PathResolver.java
index 979854ad..3b0aee15 100644
--- a/src/main/java/org/apache/commons/scxml2/PathResolver.java
+++ b/src/main/java/org/apache/commons/scxml2/PathResolver.java
@@ -23,20 +23,20 @@ package org.apache.commons.scxml2;
public interface PathResolver {
/**
- * Resolve this context sensitive path to an absolute URL.
+ * Gets a PathResolver rooted at this context sensitive path.
*
* @param ctxPath Context sensitive path, can be a relative URL
- * @return Resolved path (an absolute URL) or <code>null</code>
+ * @return Returns a new resolver rooted at ctxPath
*/
- String resolvePath(String ctxPath);
+ PathResolver getResolver(String ctxPath);
/**
- * Gets a PathResolver rooted at this context sensitive path.
+ * Resolve this context sensitive path to an absolute URL.
*
* @param ctxPath Context sensitive path, can be a relative URL
- * @return Returns a new resolver rooted at ctxPath
+ * @return Resolved path (an absolute URL) or <code>null</code>
*/
- PathResolver getResolver(String ctxPath);
+ String resolvePath(String ctxPath);
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCInstance.java b/src/main/java/org/apache/commons/scxml2/SCInstance.java
index a3f80640..3d678adf 100644
--- a/src/main/java/org/apache/commons/scxml2/SCInstance.java
+++ b/src/main/java/org/apache/commons/scxml2/SCInstance.java
@@ -145,158 +145,6 @@ public class SCInstance implements Serializable {
this.currentStatus = new Status(stateConfiguration);
}
- /**
- * (re)Initializes the state machine instance, clearing all variable contexts, histories and current status,
- * and clones the SCXML root datamodel into the root context.
- * @throws ModelException if the state machine hasn't been setup for this instance
- */
- protected void initialize() throws ModelException {
- running = false;
- if (stateMachine == null) {
- throw new ModelException(ERR_NO_STATE_MACHINE);
- }
- if (evaluator == null) {
- evaluator = EvaluatorFactory.getEvaluator(stateMachine);
- }
- if (evaluator.requiresGlobalContext()) {
- singleContext = true;
- }
- if (stateMachine.getDatamodelName() != null && !stateMachine.getDatamodelName().equals(evaluator.getSupportedDatamodel())) {
- throw new ModelException("Incompatible SCXML document datamodel \""+stateMachine.getDatamodelName()+"\""
- + " for evaluator "+evaluator.getClass().getName()+" supported datamodel \""+evaluator.getSupportedDatamodel()+"\"");
- }
- if (errorReporter == null) {
- throw new ModelException(ERR_NO_ERROR_REPORTER);
- }
- systemContext = null;
- globalContext = null;
- contexts.clear();
- histories.clear();
- stateConfiguration.clear();
-
- initialized = true;
- }
-
- protected void initializeDatamodel(final Map<String, Object> data) {
- if (globalContext == null) {
- // Clone root datamodel
- final Datamodel rootdm = stateMachine.getDatamodel();
- cloneDatamodel(rootdm, getGlobalContext(), evaluator, errorReporter);
-
- // initialize/override global context data
- if (data != null) {
- for (final String key : data.keySet()) {
- if (globalContext.has(key)) {
- globalContext.set(key, data.get(key));
- }
- }
- }
- if (stateMachine.isLateBinding() == null || Boolean.FALSE.equals(stateMachine.isLateBinding())) {
- // early binding
- for (final EnterableState es : stateMachine.getChildren()) {
- getContext(es);
- }
- }
- }
- }
-
- /**
- * Detach this state machine instance to allow external serialization.
- * <p>
- * This clears the internal I/O processor, evaluator and errorReporter members.
- * </p>
- */
- protected void detach() {
- this.internalIOProcessor = null;
- this.evaluator = null;
- this.errorReporter = null;
- }
-
- /**
- * Sets the I/O Processor for the internal event queue
- * @param internalIOProcessor the I/O Processor
- */
- protected void setInternalIOProcessor(final SCXMLIOProcessor internalIOProcessor) {
- this.internalIOProcessor = internalIOProcessor;
- }
-
- /**
- * Sets or re-attach the evaluator
- * <p>
- * If not re-attaching and this state machine instance has been initialized before,
- * it will be initialized again, destroying all existing state!
- * </p>
- * @param evaluator The evaluator for this state machine instance
- * @param reAttach Flag whether or not re-attaching it
- * @throws ModelException if {@code evaluator} is null
- */
- protected void setEvaluator(final Evaluator evaluator, final boolean reAttach) throws ModelException {
- this.evaluator = evaluator;
- if (initialized) {
- if (!reAttach) {
- // change of evaluator after initialization: re-initialize
- initialize();
- }
- else if (evaluator == null) {
- throw new ModelException("SCInstance: re-attached without Evaluator");
- }
- }
- }
-
- /**
- * @return Return the current evaluator
- */
- protected Evaluator getEvaluator() {
- return evaluator;
- }
-
- /**
- * Sets or re-attach the error reporter
- * @param errorReporter The error reporter for this state machine instance.
- * @throws ModelException if an attempt is made to set a null value for the error reporter
- */
- protected void setErrorReporter(final ErrorReporter errorReporter) throws ModelException {
- if (errorReporter == null) {
- throw new ModelException(ERR_NO_ERROR_REPORTER);
- }
- this.errorReporter = errorReporter;
- }
-
- /**
- * @return Return the state machine for this instance
- */
- public SCXML getStateMachine() {
- return stateMachine;
- }
-
- /**
- * Sets the state machine for this instance.
- * <p>
- * If this state machine instance has been initialized before, it will be initialized again, destroying all existing
- * state!
- * </p>
- * @param stateMachine The state machine for this instance
- * @throws ModelException if an attempt is made to set a null value for the state machine
- */
- protected void setStateMachine(final SCXML stateMachine) throws ModelException {
- if (stateMachine == null) {
- throw new ModelException(ERR_NO_STATE_MACHINE);
- }
- this.stateMachine = stateMachine;
- initialize();
- }
-
- public void setSingleContext(final boolean singleContext) throws ModelException {
- if (initialized) {
- throw new ModelException("SCInstance: already initialized");
- }
- this.singleContext = singleContext;
- }
-
- public boolean isSingleContext() {
- return singleContext;
- }
-
/**
* Clone data model.
*
@@ -378,10 +226,45 @@ public class SCInstance implements Serializable {
}
/**
- * @return Returns the state configuration for this instance
+ * Detach this state machine instance to allow external serialization.
+ * <p>
+ * This clears the internal I/O processor, evaluator and errorReporter members.
+ * </p>
*/
- public StateConfiguration getStateConfiguration() {
- return stateConfiguration;
+ protected void detach() {
+ this.internalIOProcessor = null;
+ this.evaluator = null;
+ this.errorReporter = null;
+ }
+
+ /**
+ * Gets the context for an EnterableState or create one if not created before.
+ *
+ * @param state The EnterableState.
+ * @return The context.
+ */
+ public Context getContext(final EnterableState state) {
+ Context context = contexts.get(state);
+ if (context == null) {
+ if (singleContext) {
+ context = getGlobalContext();
+ }
+ else {
+ final EnterableState parent = state.getParent();
+ if (parent == null) {
+ // docroot
+ context = evaluator.newContext(getGlobalContext());
+ } else {
+ context = evaluator.newContext(getContext(parent));
+ }
+ }
+ if (state instanceof TransitionalState) {
+ final Datamodel datamodel = ((TransitionalState)state).getDatamodel();
+ cloneDatamodel(datamodel, context, evaluator, errorReporter);
+ }
+ contexts.put(state, context);
+ }
+ return context;
}
/**
@@ -392,28 +275,38 @@ public class SCInstance implements Serializable {
}
/**
- * @return Returns if the state machine is running
+ * @return Return the current evaluator
*/
- public boolean isRunning() {
- return running;
+ protected Evaluator getEvaluator() {
+ return evaluator;
}
/**
- * Starts the state machine, {@link #isRunning()} hereafter will return true
- * @throws IllegalStateException Exception thrown if trying to start the state machine when in a Final state
+ * @return Returns the global context, which is the top context <em>within</em> the state machine.
*/
- public void start() throws IllegalStateException {
- if (!this.running && currentStatus.isFinal()) {
- throw new IllegalStateException("The state machine is in a Final state and cannot be set running again");
+ public Context getGlobalContext() {
+ if (globalContext == null) {
+ // force initialization of systemContext
+ getSystemContext();
+ if (systemContext != null) {
+ globalContext = evaluator.newContext(systemContext);
+ }
}
- this.running = true;
+ return globalContext;
}
/**
- * Stops the state machine, {@link #isRunning()} hereafter will return false
+ * Gets the last configuration for this history.
+ *
+ * @param history The history.
+ * @return Returns the lastConfiguration.
*/
- public void stop() {
- this.running = false;
+ public Set<EnterableState> getLastConfiguration(final History history) {
+ Set<EnterableState> lastConfiguration = histories.get(history);
+ if (lastConfiguration == null) {
+ lastConfiguration = Collections.emptySet();
+ }
+ return lastConfiguration;
}
/**
@@ -429,17 +322,17 @@ public class SCInstance implements Serializable {
}
/**
- * Sets or replace the root context.
- * @param context The new root context.
+ * @return Returns the state configuration for this instance
*/
- protected void setRootContext(final Context context) {
- this.rootContext = context;
- // force initialization of rootContext
- getRootContext();
- if (systemContext != null) {
- // re-parent the system context
- systemContext.setSystemContext(new SimpleContext(rootContext));
- }
+ public StateConfiguration getStateConfiguration() {
+ return stateConfiguration;
+ }
+
+ /**
+ * @return Return the state machine for this instance
+ */
+ public SCXML getStateMachine() {
+ return stateMachine;
}
/**
@@ -464,47 +357,69 @@ public class SCInstance implements Serializable {
}
/**
- * @return Returns the global context, which is the top context <em>within</em> the state machine.
+ * (re)Initializes the state machine instance, clearing all variable contexts, histories and current status,
+ * and clones the SCXML root datamodel into the root context.
+ * @throws ModelException if the state machine hasn't been setup for this instance
*/
- public Context getGlobalContext() {
- if (globalContext == null) {
- // force initialization of systemContext
- getSystemContext();
- if (systemContext != null) {
- globalContext = evaluator.newContext(systemContext);
- }
+ protected void initialize() throws ModelException {
+ running = false;
+ if (stateMachine == null) {
+ throw new ModelException(ERR_NO_STATE_MACHINE);
}
- return globalContext;
+ if (evaluator == null) {
+ evaluator = EvaluatorFactory.getEvaluator(stateMachine);
+ }
+ if (evaluator.requiresGlobalContext()) {
+ singleContext = true;
+ }
+ if (stateMachine.getDatamodelName() != null && !stateMachine.getDatamodelName().equals(evaluator.getSupportedDatamodel())) {
+ throw new ModelException("Incompatible SCXML document datamodel \""+stateMachine.getDatamodelName()+"\""
+ + " for evaluator "+evaluator.getClass().getName()+" supported datamodel \""+evaluator.getSupportedDatamodel()+"\"");
+ }
+ if (errorReporter == null) {
+ throw new ModelException(ERR_NO_ERROR_REPORTER);
+ }
+ systemContext = null;
+ globalContext = null;
+ contexts.clear();
+ histories.clear();
+ stateConfiguration.clear();
+
+ initialized = true;
}
- /**
- * Gets the context for an EnterableState or create one if not created before.
- *
- * @param state The EnterableState.
- * @return The context.
- */
- public Context getContext(final EnterableState state) {
- Context context = contexts.get(state);
- if (context == null) {
- if (singleContext) {
- context = getGlobalContext();
- }
- else {
- final EnterableState parent = state.getParent();
- if (parent == null) {
- // docroot
- context = evaluator.newContext(getGlobalContext());
- } else {
- context = evaluator.newContext(getContext(parent));
+ protected void initializeDatamodel(final Map<String, Object> data) {
+ if (globalContext == null) {
+ // Clone root datamodel
+ final Datamodel rootdm = stateMachine.getDatamodel();
+ cloneDatamodel(rootdm, getGlobalContext(), evaluator, errorReporter);
+
+ // initialize/override global context data
+ if (data != null) {
+ for (final String key : data.keySet()) {
+ if (globalContext.has(key)) {
+ globalContext.set(key, data.get(key));
+ }
}
}
- if (state instanceof TransitionalState) {
- final Datamodel datamodel = ((TransitionalState)state).getDatamodel();
- cloneDatamodel(datamodel, context, evaluator, errorReporter);
+ if (stateMachine.isLateBinding() == null || Boolean.FALSE.equals(stateMachine.isLateBinding())) {
+ // early binding
+ for (final EnterableState es : stateMachine.getChildren()) {
+ getContext(es);
+ }
}
- contexts.put(state, context);
}
- return context;
+ }
+
+ /**
+ * @return Returns if the state machine is running
+ */
+ public boolean isRunning() {
+ return running;
+ }
+
+ public boolean isSingleContext() {
+ return singleContext;
}
/**
@@ -519,6 +434,17 @@ public class SCInstance implements Serializable {
return contexts.get(state);
}
+ /**
+ * Resets the history state.
+ *
+ * <p>Note: used for testing purposes only</p>
+ *
+ * @param history The history.
+ */
+ public void resetConfiguration(final History history) {
+ histories.remove(history);
+ }
+
/**
* Sets the context for an EnterableState
*
@@ -533,17 +459,46 @@ public class SCInstance implements Serializable {
}
/**
- * Gets the last configuration for this history.
- *
- * @param history The history.
- * @return Returns the lastConfiguration.
+ * Sets or re-attach the error reporter
+ * @param errorReporter The error reporter for this state machine instance.
+ * @throws ModelException if an attempt is made to set a null value for the error reporter
*/
- public Set<EnterableState> getLastConfiguration(final History history) {
- Set<EnterableState> lastConfiguration = histories.get(history);
- if (lastConfiguration == null) {
- lastConfiguration = Collections.emptySet();
+ protected void setErrorReporter(final ErrorReporter errorReporter) throws ModelException {
+ if (errorReporter == null) {
+ throw new ModelException(ERR_NO_ERROR_REPORTER);
}
- return lastConfiguration;
+ this.errorReporter = errorReporter;
+ }
+
+ /**
+ * Sets or re-attach the evaluator
+ * <p>
+ * If not re-attaching and this state machine instance has been initialized before,
+ * it will be initialized again, destroying all existing state!
+ * </p>
+ * @param evaluator The evaluator for this state machine instance
+ * @param reAttach Flag whether or not re-attaching it
+ * @throws ModelException if {@code evaluator} is null
+ */
+ protected void setEvaluator(final Evaluator evaluator, final boolean reAttach) throws ModelException {
+ this.evaluator = evaluator;
+ if (initialized) {
+ if (!reAttach) {
+ // change of evaluator after initialization: re-initialize
+ initialize();
+ }
+ else if (evaluator == null) {
+ throw new ModelException("SCInstance: re-attached without Evaluator");
+ }
+ }
+ }
+
+ /**
+ * Sets the I/O Processor for the internal event queue
+ * @param internalIOProcessor the I/O Processor
+ */
+ protected void setInternalIOProcessor(final SCXMLIOProcessor internalIOProcessor) {
+ this.internalIOProcessor = internalIOProcessor;
}
/**
@@ -558,14 +513,59 @@ public class SCInstance implements Serializable {
}
/**
- * Resets the history state.
- *
- * <p>Note: used for testing purposes only</p>
- *
- * @param history The history.
+ * Sets or replace the root context.
+ * @param context The new root context.
*/
- public void resetConfiguration(final History history) {
- histories.remove(history);
+ protected void setRootContext(final Context context) {
+ this.rootContext = context;
+ // force initialization of rootContext
+ getRootContext();
+ if (systemContext != null) {
+ // re-parent the system context
+ systemContext.setSystemContext(new SimpleContext(rootContext));
+ }
+ }
+
+ public void setSingleContext(final boolean singleContext) throws ModelException {
+ if (initialized) {
+ throw new ModelException("SCInstance: already initialized");
+ }
+ this.singleContext = singleContext;
+ }
+
+ /**
+ * Sets the state machine for this instance.
+ * <p>
+ * If this state machine instance has been initialized before, it will be initialized again, destroying all existing
+ * state!
+ * </p>
+ * @param stateMachine The state machine for this instance
+ * @throws ModelException if an attempt is made to set a null value for the state machine
+ */
+ protected void setStateMachine(final SCXML stateMachine) throws ModelException {
+ if (stateMachine == null) {
+ throw new ModelException(ERR_NO_STATE_MACHINE);
+ }
+ this.stateMachine = stateMachine;
+ initialize();
+ }
+
+ /**
+ * Starts the state machine, {@link #isRunning()} hereafter will return true
+ * @throws IllegalStateException Exception thrown if trying to start the state machine when in a Final state
+ */
+ public void start() throws IllegalStateException {
+ if (!this.running && currentStatus.isFinal()) {
+ throw new IllegalStateException("The state machine is in a Final state and cannot be set running again");
+ }
+ this.running = true;
+ }
+
+ /**
+ * Stops the state machine, {@link #isRunning()} hereafter will return false
+ */
+ public void stop() {
+ this.running = false;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCInstanceObjectInputStream.java b/src/main/java/org/apache/commons/scxml2/SCInstanceObjectInputStream.java
index 98adec97..67e5e5a5 100644
--- a/src/main/java/org/apache/commons/scxml2/SCInstanceObjectInputStream.java
+++ b/src/main/java/org/apache/commons/scxml2/SCInstanceObjectInputStream.java
@@ -57,6 +57,14 @@ public class SCInstanceObjectInputStream extends ObjectInputStream {
super(in);
}
+ @Override
+ protected Class resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
+ if (classResolver != null) {
+ return classResolver.resolveClass(osc);
+ }
+ return super.resolveClass(osc);
+ }
+
/**
* Sets custom class resolver callback, or null when no longer needed.
* <p>
@@ -86,12 +94,4 @@ public class SCInstanceObjectInputStream extends ObjectInputStream {
this.classResolver = classResolver;
return old;
}
-
- @Override
- protected Class resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
- if (classResolver != null) {
- return classResolver.resolveClass(osc);
- }
- return super.resolveClass(osc);
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java b/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java
index cce5565c..a36d929f 100644
--- a/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java
+++ b/src/main/java/org/apache/commons/scxml2/SCXMLExecutionContext.java
@@ -160,81 +160,81 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
registerInvokerClass(SCXML_INVOKER_TYPE, SimpleSCXMLInvoker.class);
}
- public SCXMLExecutor getSCXMLExecutor() {
- return scxmlExecutor;
- }
-
- public SCXMLIOProcessor getExternalIOProcessor() {
- return externalIOProcessor;
- }
-
- public SCXMLIOProcessor getInternalIOProcessor() {
- return this;
- }
-
- /**
- * @return Returns the restricted execution context for actions
- */
- public ActionExecutionContext getActionExecutionContext() {
- return actionExecutionContext;
- }
-
- /**
- * Sets if the SCXML configuration should be checked before execution (default = true)
- * @param checkLegalConfiguration flag to set
- */
- public void setCheckLegalConfiguration(final boolean checkLegalConfiguration) {
- this.checkLegalConfiguration = checkLegalConfiguration;
- }
-
/**
- * @return if the SCXML configuration will be checked before execution
+ * Add an event to the internal event queue
+ * @param event The event
*/
- public boolean isCheckLegalConfiguration() {
- return checkLegalConfiguration;
+ @Override
+ public void addEvent(final TriggerEvent event) {
+ internalEventQueue.add(event);
}
/**
- * Initialize will cancel all current active Invokers, clear the internal event queue and (re)initialize the state machine
- * @param data input model map to initialize the data model with
- * @throws ModelException if the state machine hasn't been setup for the internal SCXML instance
+ * Re-attach a previously detached SCInstance.
+ * <p>
+ * Note: an already attached instance will get overwritten (and thus lost).
+ * </p>
+ * @param instance An previously detached SCInstance
*/
- public void initialize(final Map<String, Object> data) throws ModelException {
- if (!invokeIds.isEmpty()) {
- for (final Invoke invoke : new ArrayList<>(invokeIds.keySet())) {
- cancelInvoker(invoke);
+ protected void attachInstance(final SCInstance instance) {
+ if (scInstance != null ) {
+ scInstance.detach();
+ }
+ scInstance = instance;
+ if (scInstance != null) {
+ scInstance.detach();
+ try {
+ scInstance.setInternalIOProcessor(this);
+ scInstance.setEvaluator(evaluator, true);
+ scInstance.setErrorReporter(errorReporter);
+ initializeIOProcessors();
+ }
+ catch (final ModelException me) {
+ // should not happen
}
}
- internalEventQueue.clear();
- scInstance.initialize();
- initializeIOProcessors();
- scInstance.initializeDatamodel(data);
}
+
/**
- * (re)start the state machine.
+ * Cancel and remove an active Invoker
+ *
+ * @param invoke The Invoke for the Invoker to cancel
*/
- public void start() {
- if (scInstance.isRunning()) {
- throw new IllegalStateException("The state machine has already started.");
- }
- if (scInstance.getGlobalContext() == null) {
- throw new IllegalStateException("The state machine has not been initialized yet.");
+ public void cancelInvoker(final Invoke invoke) {
+ final String invokeId = invokeIds.get(invoke);
+ if (invokeId != null) {
+ try {
+ invokers.get(invokeId).cancel();
+ } catch (final InvokerException ie) {
+ final TriggerEvent te = new EventBuilder("failed.invoke.cancel."+invokeId, TriggerEvent.ERROR_EVENT).build();
+ addEvent(te);
+ }
+ removeInvoker(invoke);
}
- scInstance.start();
}
/**
- * @return Returns true if this state machine is running
+ * Detach the current SCInstance to allow external serialization.
+ * <p>
+ * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
+ * </p>
+ * @return the detached instance
*/
- public boolean isRunning() {
- return scInstance.isRunning();
+ protected SCInstance detachInstance() {
+ final SCInstance instance = scInstance;
+ scInstance.detach();
+ final Map<String, Object> systemVars = scInstance.getSystemContext().getVars();
+ systemVars.remove(SCXMLSystemContext.IOPROCESSORS_KEY);
+ systemVars.remove(SCXMLSystemContext.EVENT_KEY);
+ scInstance = null;
+ return instance;
}
/**
- * Stop the state machine
+ * @return Returns the restricted execution context for actions
*/
- public void stop() {
- scInstance.stop();
+ public ActionExecutionContext getActionExecutionContext() {
+ return actionExecutionContext;
}
/**
@@ -245,101 +245,100 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
}
/**
- * @return Returns the state machine
+ * @return Returns the error reporter
*/
- public SCXML getStateMachine() {
- return scInstance.getStateMachine();
+ public ErrorReporter getErrorReporter() {
+ return errorReporter;
}
-
/**
- * Sets or replace the state machine to be executed
- * <p>
- * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
- * state!
- * </p>
- * @param stateMachine The state machine to set
- * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ * @return Returns The evaluator.
*/
- protected void setStateMachine(final SCXML stateMachine) throws ModelException {
- scInstance.setStateMachine(stateMachine);
- // synchronize possible derived evaluator
- this.evaluator = scInstance.getEvaluator();
- initializeIOProcessors();
+ public Evaluator getEvaluator() {
+ return evaluator;
}
/**
- * @return Returns the SCInstance
+ * @return Returns the event dispatcher
*/
- public SCInstance getScInstance() {
- return scInstance;
+ public EventDispatcher getEventDispatcher() {
+ return eventdispatcher;
+ }
+
+ public SCXMLIOProcessor getExternalIOProcessor() {
+ return externalIOProcessor;
+ }
+
+ public SCXMLIOProcessor getInternalIOProcessor() {
+ return this;
}
/**
- * @return Returns The evaluator.
+ * @return Returns the map of current active Invokes and their invokeId
*/
- public Evaluator getEvaluator() {
- return evaluator;
+ public Map<Invoke, String> getInvokeIds() {
+ return invokeIds;
}
/**
- * Sets or replace the evaluator
- * <p>
- * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
- * state!
- * </p>
- * @param evaluator The evaluator to set
- * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ * Gets the {@link Invoker} for this {@link Invoke}.
+ * May return <code>null</code>. A non-null {@link Invoker} will be
+ * returned if and only if the {@link Invoke} parent TransitionalState is
+ * currently active and contains the <invoke> child.
+ *
+ * @param invoke The <code>Invoke</code>.
+ * @return The Invoker.
*/
- protected void setEvaluator(final Evaluator evaluator) throws ModelException {
- scInstance.setEvaluator(evaluator, false);
- // synchronize possible derived evaluator
- this.evaluator = scInstance.getEvaluator();
- initializeIOProcessors();
+ public Invoker getInvoker(final Invoke invoke) {
+ return invokers.get(invokeIds.get(invoke));
}
/**
- * @return Returns the error reporter
+ * @return Returns the notification registry
*/
- public ErrorReporter getErrorReporter() {
- return errorReporter;
+ public NotificationRegistry getNotificationRegistry() {
+ return notificationRegistry;
}
/**
- * Sets or replace the error reporter
- *
- * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
+ * @return Returns the SCInstance
*/
- protected void setErrorReporter(final ErrorReporter errorReporter) {
- this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter();
- try {
- scInstance.setErrorReporter(errorReporter);
- }
- catch (final ModelException me) {
- // won't happen
- }
+ public SCInstance getScInstance() {
+ return scInstance;
+ }
+
+ public SCXMLExecutor getSCXMLExecutor() {
+ return scxmlExecutor;
}
/**
- * @return Returns the event dispatcher
+ * @return Returns the state machine
*/
- public EventDispatcher getEventDispatcher() {
- return eventdispatcher;
+ public SCXML getStateMachine() {
+ return scInstance.getStateMachine();
}
/**
- * Sets or replace the event dispatch
- *
- * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
+ * @return Returns true if the internal event queue isn't empty
*/
- protected void setEventdispatcher(final EventDispatcher eventdispatcher) {
- this.eventdispatcher = eventdispatcher != null ? eventdispatcher : new SimpleDispatcher();
+ public boolean hasPendingInternalEvent() {
+ return !internalEventQueue.isEmpty();
}
/**
- * @return Returns the notification registry
+ * Initialize will cancel all current active Invokers, clear the internal event queue and (re)initialize the state machine
+ * @param data input model map to initialize the data model with
+ * @throws ModelException if the state machine hasn't been setup for the internal SCXML instance
*/
- public NotificationRegistry getNotificationRegistry() {
- return notificationRegistry;
+ public void initialize(final Map<String, Object> data) throws ModelException {
+ if (!invokeIds.isEmpty()) {
+ for (final Invoke invoke : new ArrayList<>(invokeIds.keySet())) {
+ cancelInvoker(invoke);
+ }
+ }
+ internalEventQueue.clear();
+ scInstance.initialize();
+ initializeIOProcessors();
+ scInstance.initializeDatamodel(data);
}
/**
@@ -362,75 +361,17 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
}
/**
- * Detach the current SCInstance to allow external serialization.
- * <p>
- * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
- * </p>
- * @return the detached instance
- */
- protected SCInstance detachInstance() {
- final SCInstance instance = scInstance;
- scInstance.detach();
- final Map<String, Object> systemVars = scInstance.getSystemContext().getVars();
- systemVars.remove(SCXMLSystemContext.IOPROCESSORS_KEY);
- systemVars.remove(SCXMLSystemContext.EVENT_KEY);
- scInstance = null;
- return instance;
- }
-
- /**
- * Re-attach a previously detached SCInstance.
- * <p>
- * Note: an already attached instance will get overwritten (and thus lost).
- * </p>
- * @param instance An previously detached SCInstance
- */
- protected void attachInstance(final SCInstance instance) {
- if (scInstance != null ) {
- scInstance.detach();
- }
- scInstance = instance;
- if (scInstance != null) {
- scInstance.detach();
- try {
- scInstance.setInternalIOProcessor(this);
- scInstance.setEvaluator(evaluator, true);
- scInstance.setErrorReporter(errorReporter);
- initializeIOProcessors();
- }
- catch (final ModelException me) {
- // should not happen
- }
- }
- }
-
- /**
- * Trivial utility method needed for SCXML IRP test 216 which (IMO incorrectly uses http://www.w3.org/TR/scxml
- * (no trailing /) while the SCXML spec explicitly states http://www.w3.org/TR/scxml/ should be used (supported)
- * @param uri
- * @return
- */
- private String stripTrailingSlash(final String uri) {
- return uri.endsWith("/") ? uri.substring(0, uri.length()-1) : uri;
- }
-
- /**
- * Register an Invoker for this target type.
- *
- * @param type The target type (specified by "type" attribute of the invoke element).
- * @param invokerClass The Invoker class.
+ * @return if the SCXML configuration will be checked before execution
*/
- protected void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) {
- invokerClasses.put(stripTrailingSlash(type), invokerClass);
+ public boolean isCheckLegalConfiguration() {
+ return checkLegalConfiguration;
}
/**
- * Remove the Invoker registered for this target type (if there is one registered).
- *
- * @param type The target type (specified by "type" attribute of the invoke element).
+ * @return Returns true if this state machine is running
*/
- protected void unregisterInvokerClass(final String type) {
- invokerClasses.remove(stripTrailingSlash(type));
+ public boolean isRunning() {
+ return scInstance.isRunning();
}
/**
@@ -455,16 +396,10 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
}
/**
- * Gets the {@link Invoker} for this {@link Invoke}.
- * May return <code>null</code>. A non-null {@link Invoker} will be
- * returned if and only if the {@link Invoke} parent TransitionalState is
- * currently active and contains the <invoke> child.
- *
- * @param invoke The <code>Invoke</code>.
- * @return The Invoker.
+ * @return Returns the next event from the internal event queue, if available
*/
- public Invoker getInvoker(final Invoke invoke) {
- return invokers.get(invokeIds.get(invoke));
+ public TriggerEvent nextInternalEvent() {
+ return internalEventQueue.poll();
}
/**
@@ -485,6 +420,16 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
initializeIOProcessors();
}
+ /**
+ * Register an Invoker for this target type.
+ *
+ * @param type The target type (specified by "type" attribute of the invoke element).
+ * @param invokerClass The Invoker class.
+ */
+ protected void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) {
+ invokerClasses.put(stripTrailingSlash(type), invokerClass);
+ }
+
/**
* Remove a previously active Invoker, which must already have been canceled
* @param invoke The Invoke for the Invoker to remove
@@ -496,51 +441,106 @@ public class SCXMLExecutionContext implements SCXMLIOProcessor {
}
/**
- * @return Returns the map of current active Invokes and their invokeId
+ * Sets if the SCXML configuration should be checked before execution (default = true)
+ * @param checkLegalConfiguration flag to set
*/
- public Map<Invoke, String> getInvokeIds() {
- return invokeIds;
+ public void setCheckLegalConfiguration(final boolean checkLegalConfiguration) {
+ this.checkLegalConfiguration = checkLegalConfiguration;
}
+ /**
+ * Sets or replace the error reporter
+ *
+ * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
+ */
+ protected void setErrorReporter(final ErrorReporter errorReporter) {
+ this.errorReporter = errorReporter != null ? errorReporter : new SimpleErrorReporter();
+ try {
+ scInstance.setErrorReporter(errorReporter);
+ }
+ catch (final ModelException me) {
+ // won't happen
+ }
+ }
/**
- * Cancel and remove an active Invoker
+ * Sets or replace the evaluator
+ * <p>
+ * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
+ * state!
+ * </p>
+ * @param evaluator The evaluator to set
+ * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ */
+ protected void setEvaluator(final Evaluator evaluator) throws ModelException {
+ scInstance.setEvaluator(evaluator, false);
+ // synchronize possible derived evaluator
+ this.evaluator = scInstance.getEvaluator();
+ initializeIOProcessors();
+ }
+
+ /**
+ * Sets or replace the event dispatch
*
- * @param invoke The Invoke for the Invoker to cancel
+ * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
*/
- public void cancelInvoker(final Invoke invoke) {
- final String invokeId = invokeIds.get(invoke);
- if (invokeId != null) {
- try {
- invokers.get(invokeId).cancel();
- } catch (final InvokerException ie) {
- final TriggerEvent te = new EventBuilder("failed.invoke.cancel."+invokeId, TriggerEvent.ERROR_EVENT).build();
- addEvent(te);
- }
- removeInvoker(invoke);
+ protected void setEventdispatcher(final EventDispatcher eventdispatcher) {
+ this.eventdispatcher = eventdispatcher != null ? eventdispatcher : new SimpleDispatcher();
+ }
+
+ /**
+ * Sets or replace the state machine to be executed
+ * <p>
+ * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
+ * state!
+ * </p>
+ * @param stateMachine The state machine to set
+ * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ */
+ protected void setStateMachine(final SCXML stateMachine) throws ModelException {
+ scInstance.setStateMachine(stateMachine);
+ // synchronize possible derived evaluator
+ this.evaluator = scInstance.getEvaluator();
+ initializeIOProcessors();
+ }
+
+
+ /**
+ * (re)start the state machine.
+ */
+ public void start() {
+ if (scInstance.isRunning()) {
+ throw new IllegalStateException("The state machine has already started.");
+ }
+ if (scInstance.getGlobalContext() == null) {
+ throw new IllegalStateException("The state machine has not been initialized yet.");
}
+ scInstance.start();
}
/**
- * Add an event to the internal event queue
- * @param event The event
+ * Stop the state machine
*/
- @Override
- public void addEvent(final TriggerEvent event) {
- internalEventQueue.add(event);
+ public void stop() {
+ scInstance.stop();
}
/**
- * @return Returns the next event from the internal event queue, if available
+ * Trivial utility method needed for SCXML IRP test 216 which (IMO incorrectly uses http://www.w3.org/TR/scxml
+ * (no trailing /) while the SCXML spec explicitly states http://www.w3.org/TR/scxml/ should be used (supported)
+ * @param uri
+ * @return
*/
- public TriggerEvent nextInternalEvent() {
- return internalEventQueue.poll();
+ private String stripTrailingSlash(final String uri) {
+ return uri.endsWith("/") ? uri.substring(0, uri.length()-1) : uri;
}
/**
- * @return Returns true if the internal event queue isn't empty
+ * Remove the Invoker registered for this target type (if there is one registered).
+ *
+ * @param type The target type (specified by "type" attribute of the invoke element).
*/
- public boolean hasPendingInternalEvent() {
- return !internalEventQueue.isEmpty();
+ protected void unregisterInvokerClass(final String type) {
+ invokerClasses.remove(stripTrailingSlash(type));
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java b/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java
index 43d38344..cfe0fc30 100644
--- a/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java
+++ b/src/main/java/org/apache/commons/scxml2/SCXMLExecutor.java
@@ -125,78 +125,68 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * @return the parent SCXMLIOProcessor (if any)
+ * Add a new external event, which may be done concurrently, and even when the current SCInstance is detached.
+ * <p>
+ * No processing of the vent will be done, until the next triggerEvent methods is invoked.
+ * </p>
+ * @param evt an external event
*/
- public ParentSCXMLIOProcessor getParentSCXMLIOProcessor() {
- return parentSCXMLIOProcessor;
+ @Override
+ public void addEvent(final TriggerEvent evt) {
+ if (evt != null) {
+ externalEventQueue.add(evt);
+ }
}
/**
- * Gets the current state machine instance status.
+ * Add a listener to the {@link Observable}.
*
- * @return The current Status
+ * @param observable The {@link Observable} to attach the listener to.
+ * @param listener The SCXMLListener.
*/
- public synchronized Status getStatus() {
- return exctx.getScInstance().getCurrentStatus();
+ public void addListener(final Observable observable, final SCXMLListener listener) {
+ exctx.getNotificationRegistry().addListener(observable, listener);
}
/**
- * @return the (optionally) <final><donedata/></final> produced data after the current statemachine
- * completed its execution.
+ * Re-attach a previously detached SCInstance.
+ * <p>
+ * Note: an already attached instance will get overwritten (and thus lost).
+ * </p>
+ * @param instance An previously detached SCInstance
*/
- public Object getFinalDoneData() {
- return getGlobalContext().getSystemContext().getPlatformVariables().get(SCXMLSystemContext.FINAL_DONE_DATA_KEY);
+ public void attachInstance(final SCInstance instance) {
+ exctx.attachInstance(instance);
}
/**
- * starts the state machine with a specific active configuration, as the result of a (first) step
+ * Detach the current SCInstance to allow external serialization.
* <p>
- * This will first (re)initialize the current state machine: clearing all variable contexts, histories and current
- * status, and clones the SCXML root datamodel into the root context.
+ * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
* </p>
- * @param atomicStateIds The set of atomic state ids for the state machine
- * @throws ModelException when the state machine hasn't been properly configured yet, when an unknown or illegal
- * stateId is specified, or when the specified active configuration does not represent a legal configuration.
- * @see SCInstance#initialize()
- * @see SCXMLSemantics#isLegalConfiguration(java.util.Set, ErrorReporter)
+ * <p>
+ * Note: until an instance is re-attached, no operations are allowed (and probably throw exceptions) except
+ * for {@link #addEvent(TriggerEvent)} which might still be used (concurrently) by running Invokers, or
+ * {@link #hasPendingEvents()} to check for possible pending events.
+ * </p>
+ * @return the detached instance
*/
- public synchronized void setConfiguration(final Set<String> atomicStateIds) throws ModelException {
- semantics.initialize(exctx, Collections.emptyMap());
- final Set<EnterableState> states = new HashSet<>();
- for (final String stateId : atomicStateIds) {
- final TransitionTarget tt = getStateMachine().getTargets().get(stateId);
- if (!(tt instanceof EnterableState) || !((EnterableState)tt).isAtomicState()) {
- throw new ModelException("Illegal atomic stateId "+stateId+": state unknown or not an atomic state");
- }
- EnterableState es = (EnterableState)tt;
- while (es != null && !states.add(es)) {
- es = es.getParent();
- }
- }
- if (!semantics.isLegalConfiguration(states, getErrorReporter())) {
- throw new ModelException("Illegal state machine configuration!");
- }
- for (final EnterableState es : states) {
- exctx.getScInstance().getStateConfiguration().enterState(es);
- }
+ public SCInstance detachInstance() {
+ return exctx.detachInstance();
+ }
+
+ protected void eventStep(final TriggerEvent event) throws ModelException {
+ semantics.nextStep(exctx, event);
logState();
- exctx.start();
}
/**
- * Sets or replace the expression evaluator
- * <p>
- * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
- * state!
- * </p>
- * <p>
- * Also the external event queue will be cleared.
- * </p>
- * @param evaluator The evaluator to set
- * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ * Gets the environment specific error reporter.
+ *
+ * @return Returns the errorReporter.
*/
- public void setEvaluator(final Evaluator evaluator) throws ModelException {
- exctx.setEvaluator(evaluator);
+ public ErrorReporter getErrorReporter() {
+ return exctx.getErrorReporter();
}
/**
@@ -209,15 +199,20 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Gets the root context for the state machine execution.
- * <p>
- * The root context can be used for providing external data to the state machine
- * </p>
+ * Gets the event dispatcher.
*
- * @return Context The root context.
+ * @return Returns the eventdispatcher.
*/
- public Context getRootContext() {
- return exctx.getScInstance().getRootContext();
+ public EventDispatcher getEventdispatcher() {
+ return exctx.getEventDispatcher();
+ }
+
+ /**
+ * @return the (optionally) <final><donedata/></final> produced data after the current statemachine
+ * completed its execution.
+ */
+ public Object getFinalDoneData() {
+ return getGlobalContext().getSystemContext().getPlatformVariables().get(SCXMLSystemContext.FINAL_DONE_DATA_KEY);
}
/**
@@ -233,21 +228,47 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Sets the root context for the state machine execution.
- * <b>NOTE:</b> Should only be used before the executor is set in motion.
+ * Gets the notification registry.
*
- * @param rootContext The Context that ties to the host environment.
+ * @return The notification registry.
*/
- public void setRootContext(final Context rootContext) {
- exctx.getScInstance().setRootContext(rootContext);
+ public NotificationRegistry getNotificationRegistry() {
+ return exctx.getNotificationRegistry();
}
- public void setSingleContext(final boolean singleContext) throws ModelException {
- getSCInstance().setSingleContext(singleContext);
+ /**
+ * @return the parent SCXMLIOProcessor (if any)
+ */
+ public ParentSCXMLIOProcessor getParentSCXMLIOProcessor() {
+ return parentSCXMLIOProcessor;
}
- public boolean isSingleContext() {
- return getSCInstance().isSingleContext();
+ /**
+ * @return Returns the current number of pending external events to be processed.
+ */
+ public int getPendingEvents() {
+ return externalEventQueue.size();
+ }
+
+ /**
+ * Gets the root context for the state machine execution.
+ * <p>
+ * The root context can be used for providing external data to the state machine
+ * </p>
+ *
+ * @return Context The root context.
+ */
+ public Context getRootContext() {
+ return exctx.getScInstance().getRootContext();
+ }
+
+ /**
+ * Gets the state chart instance for this executor.
+ *
+ * @return The SCInstance for this executor.
+ */
+ protected SCInstance getSCInstance() {
+ return exctx.getScInstance();
}
/**
@@ -267,64 +288,42 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Sets or replace the state machine to be executed
- * <p>
- * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
- * state!
- * </p>
- * <p>
- * Also the external event queue will be cleared.
- * </p>
- * @param stateMachine The state machine to set
- * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
- */
- public void setStateMachine(final SCXML stateMachine) throws ModelException {
- exctx.setStateMachine(semantics.normalizeStateMachine(stateMachine, exctx.getErrorReporter()));
- externalEventQueue.clear();
- }
-
- /**
- * Gets the environment specific error reporter.
+ * Gets the current state machine instance status.
*
- * @return Returns the errorReporter.
+ * @return The current Status
*/
- public ErrorReporter getErrorReporter() {
- return exctx.getErrorReporter();
+ public synchronized Status getStatus() {
+ return exctx.getScInstance().getCurrentStatus();
}
- /**
- * Sets or replace the error reporter
- *
- * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
- */
- public void setErrorReporter(final ErrorReporter errorReporter) {
- exctx.setErrorReporter(errorReporter);
+ public void go() throws ModelException {
+ go(Collections.emptyMap());
}
/**
- * Gets the event dispatcher.
- *
- * @return Returns the eventdispatcher.
+ * Clear all state, optionally initialize/override global context data, and begin executing the state machine
+ * @param data optional data to initialize/override data defined (only) in the global context of the state machine
+ * @throws ModelException if the state machine instance failed to initialize
*/
- public EventDispatcher getEventdispatcher() {
- return exctx.getEventDispatcher();
- }
+ public void go(final Map<String, Object> data) throws ModelException {
+ // first stop the state machine (flag only, otherwise start may fail hereafter)
+ exctx.stop();
+ // clear any pending external events
+ externalEventQueue.clear();
- /**
- * Sets or replace the event dispatch
- *
- * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
- */
- public void setEventdispatcher(final EventDispatcher eventdispatcher) {
- exctx.setEventdispatcher(eventdispatcher);
+ // (re)initialize
+ semantics.initialize(exctx, data);
+
+ // begin
+ semantics.firstStep(exctx);
+ logState();
}
/**
- * Sets if the SCXML configuration should be checked before execution (default = true)
- * @param checkLegalConfiguration flag to set
+ * @return Returns true if there are pending external events to be processed.
*/
- public void setCheckLegalConfiguration(final boolean checkLegalConfiguration) {
- this.exctx.setCheckLegalConfiguration(checkLegalConfiguration);
+ public boolean hasPendingEvents() {
+ return !externalEventQueue.isEmpty();
}
/**
@@ -335,33 +334,29 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Gets the notification registry.
- *
- * @return The notification registry.
+ * @return Returns true if the state machine is running
*/
- public NotificationRegistry getNotificationRegistry() {
- return exctx.getNotificationRegistry();
+ public boolean isRunning() {
+ return exctx.isRunning();
}
- /**
- * Add a listener to the {@link Observable}.
- *
- * @param observable The {@link Observable} to attach the listener to.
- * @param listener The SCXMLListener.
- */
- public void addListener(final Observable observable, final SCXMLListener listener) {
- exctx.getNotificationRegistry().addListener(observable, listener);
+ public boolean isSingleContext() {
+ return getSCInstance().isSingleContext();
}
/**
- * Remove this listener from the {@link Observable}.
- *
- * @param observable The {@link Observable}.
- * @param listener The SCXMLListener to be removed.
+ * Log the current set of active states.
*/
- public void removeListener(final Observable observable,
- final SCXMLListener listener) {
- exctx.getNotificationRegistry().removeListener(observable, listener);
+ protected void logState() {
+ if (log.isDebugEnabled()) {
+ final StringBuilder sb = new StringBuilder("Current States: [ ");
+ for (final EnterableState es : getStatus().getStates()) {
+ sb.append(es.getId()).append(", ");
+ }
+ final int length = sb.length();
+ sb.delete(length - 2, length).append(" ]");
+ log.debug(sb.toString());
+ }
}
/**
@@ -375,69 +370,14 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Remove the Invoker registered for this target type (if there is one registered).
+ * Remove this listener from the {@link Observable}.
*
- * @param type The target type (specified by "type" attribute of the invoke element).
- */
- public void unregisterInvokerClass(final String type) {
- exctx.unregisterInvokerClass(type);
- }
-
- /**
- * Detach the current SCInstance to allow external serialization.
- * <p>
- * {@link #attachInstance(SCInstance)} can be used to re-attach a previously detached instance
- * </p>
- * <p>
- * Note: until an instance is re-attached, no operations are allowed (and probably throw exceptions) except
- * for {@link #addEvent(TriggerEvent)} which might still be used (concurrently) by running Invokers, or
- * {@link #hasPendingEvents()} to check for possible pending events.
- * </p>
- * @return the detached instance
- */
- public SCInstance detachInstance() {
- return exctx.detachInstance();
- }
-
- /**
- * Re-attach a previously detached SCInstance.
- * <p>
- * Note: an already attached instance will get overwritten (and thus lost).
- * </p>
- * @param instance An previously detached SCInstance
- */
- public void attachInstance(final SCInstance instance) {
- exctx.attachInstance(instance);
- }
-
- /**
- * @return Returns true if the state machine is running
- */
- public boolean isRunning() {
- return exctx.isRunning();
- }
-
- public void go() throws ModelException {
- go(Collections.emptyMap());
- }
-
- /**
- * Clear all state, optionally initialize/override global context data, and begin executing the state machine
- * @param data optional data to initialize/override data defined (only) in the global context of the state machine
- * @throws ModelException if the state machine instance failed to initialize
+ * @param observable The {@link Observable}.
+ * @param listener The SCXMLListener to be removed.
*/
- public void go(final Map<String, Object> data) throws ModelException {
- // first stop the state machine (flag only, otherwise start may fail hereafter)
- exctx.stop();
- // clear any pending external events
- externalEventQueue.clear();
-
- // (re)initialize
- semantics.initialize(exctx, data);
-
- // begin
- semantics.firstStep(exctx);
- logState();
+ public void removeListener(final Observable observable,
+ final SCXMLListener listener) {
+ exctx.getNotificationRegistry().removeListener(observable, listener);
}
/**
@@ -467,31 +407,111 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Add a new external event, which may be done concurrently, and even when the current SCInstance is detached.
+ * Sets if the SCXML configuration should be checked before execution (default = true)
+ * @param checkLegalConfiguration flag to set
+ */
+ public void setCheckLegalConfiguration(final boolean checkLegalConfiguration) {
+ this.exctx.setCheckLegalConfiguration(checkLegalConfiguration);
+ }
+
+ /**
+ * starts the state machine with a specific active configuration, as the result of a (first) step
* <p>
- * No processing of the vent will be done, until the next triggerEvent methods is invoked.
+ * This will first (re)initialize the current state machine: clearing all variable contexts, histories and current
+ * status, and clones the SCXML root datamodel into the root context.
* </p>
- * @param evt an external event
+ * @param atomicStateIds The set of atomic state ids for the state machine
+ * @throws ModelException when the state machine hasn't been properly configured yet, when an unknown or illegal
+ * stateId is specified, or when the specified active configuration does not represent a legal configuration.
+ * @see SCInstance#initialize()
+ * @see SCXMLSemantics#isLegalConfiguration(java.util.Set, ErrorReporter)
*/
- @Override
- public void addEvent(final TriggerEvent evt) {
- if (evt != null) {
- externalEventQueue.add(evt);
+ public synchronized void setConfiguration(final Set<String> atomicStateIds) throws ModelException {
+ semantics.initialize(exctx, Collections.emptyMap());
+ final Set<EnterableState> states = new HashSet<>();
+ for (final String stateId : atomicStateIds) {
+ final TransitionTarget tt = getStateMachine().getTargets().get(stateId);
+ if (!(tt instanceof EnterableState) || !((EnterableState)tt).isAtomicState()) {
+ throw new ModelException("Illegal atomic stateId "+stateId+": state unknown or not an atomic state");
+ }
+ EnterableState es = (EnterableState)tt;
+ while (es != null && !states.add(es)) {
+ es = es.getParent();
+ }
}
+ if (!semantics.isLegalConfiguration(states, getErrorReporter())) {
+ throw new ModelException("Illegal state machine configuration!");
+ }
+ for (final EnterableState es : states) {
+ exctx.getScInstance().getStateConfiguration().enterState(es);
+ }
+ logState();
+ exctx.start();
}
/**
- * @return Returns true if there are pending external events to be processed.
+ * Sets or replace the error reporter
+ *
+ * @param errorReporter The error reporter to set, if null a SimpleErrorReporter instance will be used instead
*/
- public boolean hasPendingEvents() {
- return !externalEventQueue.isEmpty();
+ public void setErrorReporter(final ErrorReporter errorReporter) {
+ exctx.setErrorReporter(errorReporter);
}
/**
- * @return Returns the current number of pending external events to be processed.
+ * Sets or replace the expression evaluator
+ * <p>
+ * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
+ * state!
+ * </p>
+ * <p>
+ * Also the external event queue will be cleared.
+ * </p>
+ * @param evaluator The evaluator to set
+ * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
*/
- public int getPendingEvents() {
- return externalEventQueue.size();
+ public void setEvaluator(final Evaluator evaluator) throws ModelException {
+ exctx.setEvaluator(evaluator);
+ }
+
+ /**
+ * Sets or replace the event dispatch
+ *
+ * @param eventdispatcher The event dispatcher to set, if null a SimpleDispatcher instance will be used instead
+ */
+ public void setEventdispatcher(final EventDispatcher eventdispatcher) {
+ exctx.setEventdispatcher(eventdispatcher);
+ }
+
+ /**
+ * Sets the root context for the state machine execution.
+ * <b>NOTE:</b> Should only be used before the executor is set in motion.
+ *
+ * @param rootContext The Context that ties to the host environment.
+ */
+ public void setRootContext(final Context rootContext) {
+ exctx.getScInstance().setRootContext(rootContext);
+ }
+
+ public void setSingleContext(final boolean singleContext) throws ModelException {
+ getSCInstance().setSingleContext(singleContext);
+ }
+
+ /**
+ * Sets or replace the state machine to be executed
+ * <p>
+ * If the state machine instance has been initialized before, it will be initialized again, destroying all existing
+ * state!
+ * </p>
+ * <p>
+ * Also the external event queue will be cleared.
+ * </p>
+ * @param stateMachine The state machine to set
+ * @throws ModelException if attempting to set a null value or the state machine instance failed to re-initialize
+ */
+ public void setStateMachine(final SCXML stateMachine) throws ModelException {
+ exctx.setStateMachine(semantics.normalizeStateMachine(stateMachine, exctx.getErrorReporter()));
+ externalEventQueue.clear();
}
/**
@@ -509,6 +529,17 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
triggerEvents();
}
+ /**
+ * Trigger all pending and incoming events, until there are no more pending events
+ * @throws ModelException in case there is a fatal SCXML object model problem.
+ */
+ public void triggerEvents() throws ModelException {
+ TriggerEvent evt;
+ while (exctx.isRunning() && (evt = externalEventQueue.poll()) != null) {
+ eventStep(evt);
+ }
+ }
+
/**
* The worker method.
* Re-evaluates current status whenever any events are triggered.
@@ -530,42 +561,11 @@ public class SCXMLExecutor implements SCXMLIOProcessor {
}
/**
- * Trigger all pending and incoming events, until there are no more pending events
- * @throws ModelException in case there is a fatal SCXML object model problem.
- */
- public void triggerEvents() throws ModelException {
- TriggerEvent evt;
- while (exctx.isRunning() && (evt = externalEventQueue.poll()) != null) {
- eventStep(evt);
- }
- }
-
- protected void eventStep(final TriggerEvent event) throws ModelException {
- semantics.nextStep(exctx, event);
- logState();
- }
-
- /**
- * Gets the state chart instance for this executor.
+ * Remove the Invoker registered for this target type (if there is one registered).
*
- * @return The SCInstance for this executor.
- */
- protected SCInstance getSCInstance() {
- return exctx.getScInstance();
- }
-
- /**
- * Log the current set of active states.
+ * @param type The target type (specified by "type" attribute of the invoke element).
*/
- protected void logState() {
- if (log.isDebugEnabled()) {
- final StringBuilder sb = new StringBuilder("Current States: [ ");
- for (final EnterableState es : getStatus().getStates()) {
- sb.append(es.getId()).append(", ");
- }
- final int length = sb.length();
- sb.delete(length - 2, length).append(" ]");
- log.debug(sb.toString());
- }
+ public void unregisterInvokerClass(final String type) {
+ exctx.unregisterInvokerClass(type);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLExpressionException.java b/src/main/java/org/apache/commons/scxml2/SCXMLExpressionException.java
index 15821956..b0d193aa 100644
--- a/src/main/java/org/apache/commons/scxml2/SCXMLExpressionException.java
+++ b/src/main/java/org/apache/commons/scxml2/SCXMLExpressionException.java
@@ -41,14 +41,6 @@ public class SCXMLExpressionException extends Exception {
super(message);
}
- /**
- * @see Exception#Exception(Throwable)
- * @param cause The cause
- */
- public SCXMLExpressionException(final Throwable cause) {
- super(cause);
- }
-
/**
* @see Exception#Exception(String, Throwable)
* @param message The error message
@@ -59,5 +51,13 @@ public class SCXMLExpressionException extends Exception {
super(message, cause);
}
+ /**
+ * @see Exception#Exception(Throwable)
+ * @param cause The cause
+ */
+ public SCXMLExpressionException(final Throwable cause) {
+ super(cause);
+ }
+
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLSemantics.java b/src/main/java/org/apache/commons/scxml2/SCXMLSemantics.java
index 72210949..0ee98d67 100644
--- a/src/main/java/org/apache/commons/scxml2/SCXMLSemantics.java
+++ b/src/main/java/org/apache/commons/scxml2/SCXMLSemantics.java
@@ -71,24 +71,26 @@ import org.apache.commons.scxml2.model.SCXML;
public interface SCXMLSemantics {
/**
- * Optional post processing after loading an {@link SCXML} document, invoked by {@link SCXMLExecutor}
- * when setting the {@link SCXMLExecutor#setStateMachine(SCXML)}. May be used for removing pseudo-states etc.
- *
- * @param input SCXML state machine
- * @param errRep ErrorReporter callback
- * @return normalized SCXML state machine, pseudo states are removed, etc.
- */
- SCXML normalizeStateMachine(final SCXML input, final ErrorReporter errRep);
-
- /**
- * Initialize the SCXML state machine, optionally initializing (overriding) root <datamodel><data> elements
- * with external values provided through a data map.
- * @param data A data map to initialize/override <data> elements in the root (global) <datamodel> with
- * ids matching the keys in the map (other data map entries will be ignored)
- * @param exctx The execution context to initialize
- * @throws ModelException if the state machine hasn't been setup for the internal SCXML instance
+ * The final step in the execution of an SCXML state machine.
+ * <p>
+ * This final step is corresponding to the Algorithm for SCXML processing exitInterpreter() procedure, after the
+ * state machine stopped running.
+ * </p>
+ * <p>
+ * If the state machine still is {@link SCXMLExecutionContext#isRunning()} invoking this method should simply
+ * do nothing.
+ * </p>
+ * <p>
+ * This final step should first exit all remaining active states and cancel any active invokers, before handling
+ * the possible donedata element for the last final state.
+ * </p>
+ * <p>
+ * <em>NOTE: the current implementation does not yet provide final donedata handling.</em>
+ * </p>
+ * @param exctx The execution context for this step
+ * @throws ModelException if a SCXML model error occurred during the execution.
*/
- void initialize(final SCXMLExecutionContext exctx, final Map<String, Object> data) throws ModelException;
+ void finalStep(final SCXMLExecutionContext exctx) throws ModelException;
/**
* First step in the execution of an SCXML state machine.
@@ -114,6 +116,35 @@ public interface SCXMLSemantics {
*/
void firstStep(final SCXMLExecutionContext exctx) throws ModelException;
+ /**
+ * Initialize the SCXML state machine, optionally initializing (overriding) root <datamodel><data> elements
+ * with external values provided through a data map.
+ * @param data A data map to initialize/override <data> elements in the root (global) <datamodel> with
+ * ids matching the keys in the map (other data map entries will be ignored)
+ * @param exctx The execution context to initialize
+ * @throws ModelException if the state machine hasn't been setup for the internal SCXML instance
+ */
+ void initialize(final SCXMLExecutionContext exctx, final Map<String, Object> data) throws ModelException;
+
+ /**
+ * Checks whether a given set of states is a legal Harel State Table
+ * configuration (with the respect to the definition of the OR and AND
+ * states).
+ * <p>
+ * When {@link SCXMLExecutionContext#isCheckLegalConfiguration()} is true (default) the SCXMLSemantics implementation
+ * <em>should</em> invoke this method before executing a step, and throw a ModelException if a non-legal
+ * configuration is encountered.
+ * </p>
+ * <p>
+ * This method is also first invoked when manually initializing the status of a state machine through
+ * {@link SCXMLExecutor#setConfiguration(java.util.Set)}.
+ * </p>
+ * @param states a set of states
+ * @param errRep ErrorReporter to report detailed error info if needed
+ * @return true if a given state configuration is legal, false otherwise
+ */
+ boolean isLegalConfiguration(final Set<EnterableState> states, final ErrorReporter errRep);
+
/**
* Next step in the execution of an SCXML state machine.
* <p>
@@ -142,43 +173,12 @@ public interface SCXMLSemantics {
void nextStep(final SCXMLExecutionContext exctx, final TriggerEvent event) throws ModelException;
/**
- * The final step in the execution of an SCXML state machine.
- * <p>
- * This final step is corresponding to the Algorithm for SCXML processing exitInterpreter() procedure, after the
- * state machine stopped running.
- * </p>
- * <p>
- * If the state machine still is {@link SCXMLExecutionContext#isRunning()} invoking this method should simply
- * do nothing.
- * </p>
- * <p>
- * This final step should first exit all remaining active states and cancel any active invokers, before handling
- * the possible donedata element for the last final state.
- * </p>
- * <p>
- * <em>NOTE: the current implementation does not yet provide final donedata handling.</em>
- * </p>
- * @param exctx The execution context for this step
- * @throws ModelException if a SCXML model error occurred during the execution.
- */
- void finalStep(final SCXMLExecutionContext exctx) throws ModelException;
-
- /**
- * Checks whether a given set of states is a legal Harel State Table
- * configuration (with the respect to the definition of the OR and AND
- * states).
- * <p>
- * When {@link SCXMLExecutionContext#isCheckLegalConfiguration()} is true (default) the SCXMLSemantics implementation
- * <em>should</em> invoke this method before executing a step, and throw a ModelException if a non-legal
- * configuration is encountered.
- * </p>
- * <p>
- * This method is also first invoked when manually initializing the status of a state machine through
- * {@link SCXMLExecutor#setConfiguration(java.util.Set)}.
- * </p>
- * @param states a set of states
- * @param errRep ErrorReporter to report detailed error info if needed
- * @return true if a given state configuration is legal, false otherwise
+ * Optional post processing after loading an {@link SCXML} document, invoked by {@link SCXMLExecutor}
+ * when setting the {@link SCXMLExecutor#setStateMachine(SCXML)}. May be used for removing pseudo-states etc.
+ *
+ * @param input SCXML state machine
+ * @param errRep ErrorReporter callback
+ * @return normalized SCXML state machine, pseudo states are removed, etc.
*/
- boolean isLegalConfiguration(final Set<EnterableState> states, final ErrorReporter errRep);
+ SCXML normalizeStateMachine(final SCXML input, final ErrorReporter errRep);
}
diff --git a/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java b/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java
index 806c1c35..a48974de 100644
--- a/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java
+++ b/src/main/java/org/apache/commons/scxml2/SCXMLSystemContext.java
@@ -74,24 +74,6 @@ public class SCXMLSystemContext implements Context, Serializable {
*/
private long nextSessionSequenceId;
- /**
- * Initialize or replace systemContext
- * @param systemContext the system context to set
- * @throws NullPointerException if systemContext == null
- */
- void setSystemContext(final Context systemContext) {
- if (this.systemContext != null) {
- // replace systemContext
- systemContext.getVars().putAll(this.systemContext.getVars());
- }
- else {
- // create Platform variables map
- systemContext.setLocal(X_KEY, new HashMap<String, Object>());
- }
- this.systemContext = systemContext;
- this.protectedVars = Collections.unmodifiableMap(systemContext.getVars());
- }
-
/**
* The unmodifiable wrapped variables map from the wrapped system context
*/
@@ -106,22 +88,38 @@ public class SCXMLSystemContext implements Context, Serializable {
}
@Override
- public void set(final String name, final Object value) {
- if (PROTECTED_NAMES.contains(name)) {
- throw new UnsupportedOperationException();
- }
- // non-protected variables are set on the parent of the system context (e.g. root context)
- systemContext.getParent().set(name, value);
+ public Object get(final String name) {
+ return systemContext.get(name);
+ }
+
+ /**
+ * @return Returns the wrapped (modifiable) system context
+ */
+ Context getContext() {
+ return systemContext;
}
@Override
- public void setLocal(final String name, final Object value) {
- throw new UnsupportedOperationException();
+ public Context getParent() {
+ return systemContext.getParent();
+ }
+
+ /**
+ * @return The Platform specific system variables map stored under the {@link #X_KEY _x} root system variable
+ */
+ @SuppressWarnings("unchecked")
+ public Map<String, Object> getPlatformVariables() {
+ return (Map<String, Object>)get(X_KEY);
}
@Override
- public Object get(final String name) {
- return systemContext.get(name);
+ public SCXMLSystemContext getSystemContext() {
+ return this;
+ }
+
+ @Override
+ public Map<String, Object> getVars() {
+ return protectedVars;
}
@Override
@@ -134,38 +132,40 @@ public class SCXMLSystemContext implements Context, Serializable {
return systemContext.hasLocal(name);
}
- @Override
- public Map<String, Object> getVars() {
- return protectedVars;
- }
-
@Override
public void reset() {
throw new UnsupportedOperationException();
}
@Override
- public Context getParent() {
- return systemContext.getParent();
+ public void set(final String name, final Object value) {
+ if (PROTECTED_NAMES.contains(name)) {
+ throw new UnsupportedOperationException();
+ }
+ // non-protected variables are set on the parent of the system context (e.g. root context)
+ systemContext.getParent().set(name, value);
}
@Override
- public SCXMLSystemContext getSystemContext() {
- return this;
- }
-
- /**
- * @return The Platform specific system variables map stored under the {@link #X_KEY _x} root system variable
- */
- @SuppressWarnings("unchecked")
- public Map<String, Object> getPlatformVariables() {
- return (Map<String, Object>)get(X_KEY);
+ public void setLocal(final String name, final Object value) {
+ throw new UnsupportedOperationException();
}
/**
- * @return Returns the wrapped (modifiable) system context
+ * Initialize or replace systemContext
+ * @param systemContext the system context to set
+ * @throws NullPointerException if systemContext == null
*/
- Context getContext() {
- return systemContext;
+ void setSystemContext(final Context systemContext) {
+ if (this.systemContext != null) {
+ // replace systemContext
+ systemContext.getVars().putAll(this.systemContext.getVars());
+ }
+ else {
+ // create Platform variables map
+ systemContext.setLocal(X_KEY, new HashMap<String, Object>());
+ }
+ this.systemContext = systemContext;
+ this.protectedVars = Collections.unmodifiableMap(systemContext.getVars());
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/StateConfiguration.java b/src/main/java/org/apache/commons/scxml2/StateConfiguration.java
index c47fc704..6d7f7050 100644
--- a/src/main/java/org/apache/commons/scxml2/StateConfiguration.java
+++ b/src/main/java/org/apache/commons/scxml2/StateConfiguration.java
@@ -45,22 +45,11 @@ public class StateConfiguration implements Serializable {
private final Set<EnterableState> atomicStatesSet = Collections.unmodifiableSet(atomicStates);
/**
- * Gets the active states
- *
- * @return active states including simple states and their
- * complex ancestors up to the root.
- */
- public Set<EnterableState> getActiveStates() {
- return activeStatesSet;
- }
-
- /**
- * Gets the current atomic states (leaf only).
- *
- * @return Returns the atomic states - simple (leaf) states only.
+ * Clear the state configuration
*/
- public Set<EnterableState> getStates() {
- return atomicStatesSet;
+ public void clear() {
+ activeStates.clear();
+ atomicStates.clear();
}
/**
@@ -92,10 +81,21 @@ public class StateConfiguration implements Serializable {
}
/**
- * Clear the state configuration
+ * Gets the active states
+ *
+ * @return active states including simple states and their
+ * complex ancestors up to the root.
*/
- public void clear() {
- activeStates.clear();
- atomicStates.clear();
+ public Set<EnterableState> getActiveStates() {
+ return activeStatesSet;
+ }
+
+ /**
+ * Gets the current atomic states (leaf only).
+ *
+ * @return Returns the atomic states - simple (leaf) states only.
+ */
+ public Set<EnterableState> getStates() {
+ return atomicStatesSet;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/Status.java b/src/main/java/org/apache/commons/scxml2/Status.java
index dddb15cc..b1d15adb 100644
--- a/src/main/java/org/apache/commons/scxml2/Status.java
+++ b/src/main/java/org/apache/commons/scxml2/Status.java
@@ -40,10 +40,13 @@ public class Status implements Serializable {
}
/**
- * @return Whether the state machine terminated AND we reached a top level Final state.
+ * Gets the active states configuration.
+ *
+ * @return active states configuration including simple states and their
+ * complex ancestors up to the root.
*/
- public boolean isFinal() {
- return getFinalState() != null;
+ public Set<EnterableState> getActiveStates() {
+ return configuration.getActiveStates();
}
/**
@@ -69,13 +72,10 @@ public class Status implements Serializable {
}
/**
- * Gets the active states configuration.
- *
- * @return active states configuration including simple states and their
- * complex ancestors up to the root.
+ * @return Whether the state machine terminated AND we reached a top level Final state.
*/
- public Set<EnterableState> getActiveStates() {
- return configuration.getActiveStates();
+ public boolean isFinal() {
+ return getFinalState() != null;
}
public boolean isInState(final String state) {
diff --git a/src/main/java/org/apache/commons/scxml2/TriggerEvent.java b/src/main/java/org/apache/commons/scxml2/TriggerEvent.java
index 620a4aff..d9b111a0 100644
--- a/src/main/java/org/apache/commons/scxml2/TriggerEvent.java
+++ b/src/main/java/org/apache/commons/scxml2/TriggerEvent.java
@@ -31,28 +31,6 @@ public class TriggerEvent implements Serializable {
/** Serial version UID. */
private static final long serialVersionUID = 1L;
- /**
- * Constructor.
- *
- * @param name The event name
- * @param type The event type
- * @deprecated use {@link EventBuilder instead}
- */
- public TriggerEvent(final String name, final int type) {
- this(name, type, null, null, null, null, null);
- }
-
- TriggerEvent(final String name, final int type, final String sendId, final String origin,
- final String originType, final String invokeId, final Object data) {
- this.name = name != null ? name.trim() : "";
- this.type = type;
- this.sendId = sendId;
- this.origin = origin;
- this.originType = originType;
- this.invokeId = invokeId;
- this.data = data;
- }
-
/**
* <code>CALL_EVENT</code>.
*/
@@ -120,23 +98,66 @@ public class TriggerEvent implements Serializable {
public static final String ERROR_PLATFORM = "error.platform";
private final String name;
+
private final int type;
+
private final String sendId;
private final String origin;
private final String originType;
private final String invokeId;
private final Object data;
+ /**
+ * Constructor.
+ *
+ * @param name The event name
+ * @param type The event type
+ * @deprecated use {@link EventBuilder instead}
+ */
+ public TriggerEvent(final String name, final int type) {
+ this(name, type, null, null, null, null, null);
+ }
+ TriggerEvent(final String name, final int type, final String sendId, final String origin,
+ final String originType, final String invokeId, final Object data) {
+ this.name = name != null ? name.trim() : "";
+ this.type = type;
+ this.sendId = sendId;
+ this.origin = origin;
+ this.originType = originType;
+ this.invokeId = invokeId;
+ this.data = data;
+ }
- public String getName() {
- return name;
+ /**
+ * Define an equals operator for TriggerEvent.
+ *
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof TriggerEvent) {
+ final TriggerEvent te2 = (TriggerEvent) obj;
+ if (type == te2.type && name.equals(te2.name) &&
+ Objects.equals(sendId, te2.sendId) &&
+ Objects.equals(origin, te2.origin) &&
+ Objects.equals(originType, te2.originType) &&
+ Objects.equals(invokeId, te2.invokeId) &&
+ Objects.equals(data, te2.data)) {
+ return true;
+ }
+ }
+ return false;
}
- public int getType() {
- return type;
+ public Object getData() {
+ return data;
}
- public String getSendId() {
- return sendId;
+ public String getInvokeId() {
+ return invokeId;
+ }
+
+ public String getName() {
+ return name;
}
public String getOrigin() {
@@ -147,33 +168,22 @@ public class TriggerEvent implements Serializable {
return originType;
}
- public String getInvokeId() {
- return invokeId;
+ public String getSendId() {
+ return sendId;
}
- public Object getData() {
- return data;
+ public int getType() {
+ return type;
}
/**
- * Define an equals operator for TriggerEvent.
+ * Returns the hash code for this TriggerEvent object.
*
- * @see Object#equals(Object)
+ * @see Object#hashCode()
*/
@Override
- public boolean equals(final Object obj) {
- if (obj instanceof TriggerEvent) {
- final TriggerEvent te2 = (TriggerEvent) obj;
- if (type == te2.type && name.equals(te2.name) &&
- Objects.equals(sendId, te2.sendId) &&
- Objects.equals(origin, te2.origin) &&
- Objects.equals(originType, te2.originType) &&
- Objects.equals(invokeId, te2.invokeId) &&
- Objects.equals(data, te2.data)) {
- return true;
- }
- }
- return false;
+ public int hashCode() {
+ return String.valueOf(this).hashCode();
}
/**
@@ -204,15 +214,5 @@ public class TriggerEvent implements Serializable {
return String.valueOf(buf);
}
- /**
- * Returns the hash code for this TriggerEvent object.
- *
- * @see Object#hashCode()
- */
- @Override
- public int hashCode() {
- return String.valueOf(this).hashCode();
- }
-
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/AbstractBaseEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/AbstractBaseEvaluator.java
index c87d9006..9cda0040 100644
--- a/src/main/java/org/apache/commons/scxml2/env/AbstractBaseEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/AbstractBaseEvaluator.java
@@ -40,25 +40,6 @@ public abstract class AbstractBaseEvaluator implements Evaluator, Serializable {
*/
private static final String ASSIGN_VARIABLE_NAME = "a"+ UUID.randomUUID().toString().replace('-','x');
- /**
- * @see Evaluator#evalAssign(Context, String, Object)
- */
- @Override
- public void evalAssign(final Context ctx, final String location, final Object data) throws SCXMLExpressionException {
- final StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME);
- try {
- ctx.getVars().put(ASSIGN_VARIABLE_NAME, data);
- eval(ctx, sb.toString());
- } catch (final SCXMLExpressionException e) {
- if (e.getCause() != null && e.getCause() != null && e.getCause().getMessage() != null) {
- throw new SCXMLExpressionException("Error evaluating assign to location=\"" + location + "\": " + e.getCause().getMessage());
- }
- throw e;
- } finally {
- ctx.getVars().remove(ASSIGN_VARIABLE_NAME);
- }
- }
-
@Override
public Object cloneData(final Object data) {
if (data != null) {
@@ -104,4 +85,23 @@ public abstract class AbstractBaseEvaluator implements Evaluator, Serializable {
protected Object cloneUnknownDataType(final Object data) {
return data.toString();
}
+
+ /**
+ * @see Evaluator#evalAssign(Context, String, Object)
+ */
+ @Override
+ public void evalAssign(final Context ctx, final String location, final Object data) throws SCXMLExpressionException {
+ final StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME);
+ try {
+ ctx.getVars().put(ASSIGN_VARIABLE_NAME, data);
+ eval(ctx, sb.toString());
+ } catch (final SCXMLExpressionException e) {
+ if (e.getCause() != null && e.getCause() != null && e.getCause().getMessage() != null) {
+ throw new SCXMLExpressionException("Error evaluating assign to location=\"" + location + "\": " + e.getCause().getMessage());
+ }
+ throw e;
+ } finally {
+ ctx.getVars().remove(ASSIGN_VARIABLE_NAME);
+ }
+ }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/AbstractStateMachine.java b/src/main/java/org/apache/commons/scxml2/env/AbstractStateMachine.java
index 3831b723..a2fa703c 100644
--- a/src/main/java/org/apache/commons/scxml2/env/AbstractStateMachine.java
+++ b/src/main/java/org/apache/commons/scxml2/env/AbstractStateMachine.java
@@ -66,19 +66,44 @@ import org.apache.commons.scxml2.model.TransitionTarget;
public abstract class AbstractStateMachine {
/**
- * The state machine that will drive the instances of this class.
+ * A SCXMLListener that is only concerned about "onentry"
+ * notifications.
*/
- private SCXML stateMachine;
+ protected class EntryListener implements SCXMLListener {
- /**
- * The instance specific SCXML engine.
- */
- private SCXMLExecutor engine;
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onEntry(final EnterableState entered) {
+ invoke(entered.getId());
+ }
- /**
- * The log.
- */
- private Log log;
+ /**
+ * No-op.
+ *
+ * @param exited The state being exited.
+ */
+ @Override
+ public void onExit(final EnterableState exited) {
+ // nothing to do
+ }
+
+ /**
+ * No-op.
+ *
+ * @param from The "source" transition target.
+ * @param to The "destination" transition target.
+ * @param transition The transition being followed.
+ * @param event The event triggering the transition
+ */
+ @Override
+ public void onTransition(final TransitionTarget from,
+ final TransitionTarget to, final Transition transition, final String event) {
+ // nothing to do
+ }
+
+ }
/**
* The method signature for the activities corresponding to each
@@ -93,41 +118,19 @@ public abstract class AbstractStateMachine {
private static final Object[] PARAMETERS = new Object[0];
/**
- * Convenience constructor, object instantiation incurs parsing cost.
- *
- * @param scxmlDocument The URL pointing to the SCXML document that
- * describes the "lifecycle" of the
- * instances of this class.
- * @throws ModelException in case there is a fatal SCXML object model problem
+ * The state machine that will drive the instances of this class.
*/
- public AbstractStateMachine(final URL scxmlDocument) throws ModelException {
- // default is JEXL
- this(scxmlDocument, new JexlContext(), new JexlEvaluator());
- }
+ private SCXML stateMachine;
/**
- * Primary constructor, object instantiation incurs parsing cost.
- *
- * @param scxmlDocument The URL pointing to the SCXML document that
- * describes the "lifecycle" of the
- * instances of this class.
- * @param rootCtx The root context for this instance.
- * @param evaluator The expression evaluator for this instance.
- * @throws ModelException in case there is a fatal SCXML object model problem
- *
- * @see Context
- * @see Evaluator
+ * The instance specific SCXML engine.
*/
- public AbstractStateMachine(final URL scxmlDocument,
- final Context rootCtx, final Evaluator evaluator) throws ModelException {
- log = LogFactory.getLog(this.getClass());
- try {
- stateMachine = SCXMLReader.read(scxmlDocument);
- } catch (IOException | XMLStreamException | ModelException ioe) {
- logError(ioe);
- }
- initialize(stateMachine, rootCtx, evaluator);
- }
+ private SCXMLExecutor engine;
+
+ /**
+ * The log.
+ */
+ private Log log;
/**
* Convenience constructor.
@@ -164,25 +167,40 @@ public abstract class AbstractStateMachine {
}
/**
- * Instantiate and initialize the underlying executor instance.
+ * Convenience constructor, object instantiation incurs parsing cost.
*
- * @param stateMachine The state machine
- * @param rootCtx The root context
- * @param evaluator The expression evaluator
+ * @param scxmlDocument The URL pointing to the SCXML document that
+ * describes the "lifecycle" of the
+ * instances of this class.
* @throws ModelException in case there is a fatal SCXML object model problem
*/
- private void initialize(final SCXML stateMachine,
+ public AbstractStateMachine(final URL scxmlDocument) throws ModelException {
+ // default is JEXL
+ this(scxmlDocument, new JexlContext(), new JexlEvaluator());
+ }
+
+ /**
+ * Primary constructor, object instantiation incurs parsing cost.
+ *
+ * @param scxmlDocument The URL pointing to the SCXML document that
+ * describes the "lifecycle" of the
+ * instances of this class.
+ * @param rootCtx The root context for this instance.
+ * @param evaluator The expression evaluator for this instance.
+ * @throws ModelException in case there is a fatal SCXML object model problem
+ *
+ * @see Context
+ * @see Evaluator
+ */
+ public AbstractStateMachine(final URL scxmlDocument,
final Context rootCtx, final Evaluator evaluator) throws ModelException {
- engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
- new SimpleErrorReporter());
- engine.setStateMachine(stateMachine);
- engine.setRootContext(rootCtx);
- engine.addListener(stateMachine, new EntryListener());
+ log = LogFactory.getLog(this.getClass());
try {
- engine.go();
- } catch (final ModelException me) {
- logError(me);
+ stateMachine = SCXMLReader.read(scxmlDocument);
+ } catch (IOException | XMLStreamException | ModelException ioe) {
+ logError(ioe);
}
+ initialize(stateMachine, rootCtx, evaluator);
}
/**
@@ -222,12 +240,25 @@ public abstract class AbstractStateMachine {
}
/**
- * Sets the log for this class.
+ * Instantiate and initialize the underlying executor instance.
*
- * @param log The log to set.
+ * @param stateMachine The state machine
+ * @param rootCtx The root context
+ * @param evaluator The expression evaluator
+ * @throws ModelException in case there is a fatal SCXML object model problem
*/
- public void setLog(final Log log) {
- this.log = log;
+ private void initialize(final SCXML stateMachine,
+ final Context rootCtx, final Evaluator evaluator) throws ModelException {
+ engine = new SCXMLExecutor(evaluator, new SimpleDispatcher(),
+ new SimpleErrorReporter());
+ engine.setStateMachine(stateMachine);
+ engine.setRootContext(rootCtx);
+ engine.addListener(stateMachine, new EntryListener());
+ try {
+ engine.go();
+ } catch (final ModelException me) {
+ logError(me);
+ }
}
/**
@@ -249,6 +280,17 @@ public abstract class AbstractStateMachine {
return true;
}
+ /**
+ * Utility method for logging error.
+ *
+ * @param exception The exception leading to this error condition.
+ */
+ protected void logError(final Exception exception) {
+ if (log.isErrorEnabled()) {
+ log.error(exception.getMessage(), exception);
+ }
+ }
+
/**
* Reset the state machine.
*
@@ -265,54 +307,12 @@ public abstract class AbstractStateMachine {
}
/**
- * Utility method for logging error.
+ * Sets the log for this class.
*
- * @param exception The exception leading to this error condition.
- */
- protected void logError(final Exception exception) {
- if (log.isErrorEnabled()) {
- log.error(exception.getMessage(), exception);
- }
- }
-
- /**
- * A SCXMLListener that is only concerned about "onentry"
- * notifications.
+ * @param log The log to set.
*/
- protected class EntryListener implements SCXMLListener {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onEntry(final EnterableState entered) {
- invoke(entered.getId());
- }
-
- /**
- * No-op.
- *
- * @param from The "source" transition target.
- * @param to The "destination" transition target.
- * @param transition The transition being followed.
- * @param event The event triggering the transition
- */
- @Override
- public void onTransition(final TransitionTarget from,
- final TransitionTarget to, final Transition transition, final String event) {
- // nothing to do
- }
-
- /**
- * No-op.
- *
- * @param exited The state being exited.
- */
- @Override
- public void onExit(final EnterableState exited) {
- // nothing to do
- }
-
+ public void setLog(final Log log) {
+ this.log = log;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java b/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
index 97b9fc39..794a68ab 100644
--- a/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
+++ b/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
@@ -61,6 +61,23 @@ public final class EffectiveContextMap extends AbstractMap<String, Object> imple
return Collections.unmodifiableMap(map).entrySet();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object get(final Object key) {
+ if (key != null) {
+ Context current = leaf;
+ while (current != null) {
+ if (current.getVars().containsKey(key.toString())) {
+ return current.getVars().get(key);
+ }
+ current = current.getParent();
+ }
+ }
+ return null;
+ }
+
/**
* Parent Context first merging of all Context vars, to ensure same named 'local' vars shadows parent var
* @param leaf current leaf Context
@@ -86,21 +103,4 @@ public final class EffectiveContextMap extends AbstractMap<String, Object> imple
}
return old;
}
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Object get(final Object key) {
- if (key != null) {
- Context current = leaf;
- while (current != null) {
- if (current.getVars().containsKey(key.toString())) {
- return current.getVars().get(key);
- }
- current = current.getParent();
- }
- }
- return null;
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/LogUtils.java b/src/main/java/org/apache/commons/scxml2/env/LogUtils.java
index 139afc59..d30c2d2e 100644
--- a/src/main/java/org/apache/commons/scxml2/env/LogUtils.java
+++ b/src/main/java/org/apache/commons/scxml2/env/LogUtils.java
@@ -24,6 +24,23 @@ import org.apache.commons.scxml2.model.TransitionTarget;
*/
public final class LogUtils {
+ /**
+ * Write out this TransitionTarget location in a XPath style format.
+ *
+ * @param tt The TransitionTarget whose "path" is to needed
+ * @return String The XPath style location of the TransitionTarget within
+ * the SCXML document
+ */
+ public static String getTTPath(final TransitionTarget tt) {
+ final StringBuilder sb = new StringBuilder("/");
+ for (int i = 0; i < tt.getNumberOfAncestors(); i++) {
+ sb.append(tt.getAncestor(i).getId());
+ sb.append("/");
+ }
+ sb.append(tt.getId());
+ return sb.toString();
+ }
+
/**
* Create a human readable log view of this transition.
*
@@ -42,23 +59,6 @@ public final class LogUtils {
')';
}
- /**
- * Write out this TransitionTarget location in a XPath style format.
- *
- * @param tt The TransitionTarget whose "path" is to needed
- * @return String The XPath style location of the TransitionTarget within
- * the SCXML document
- */
- public static String getTTPath(final TransitionTarget tt) {
- final StringBuilder sb = new StringBuilder("/");
- for (int i = 0; i < tt.getNumberOfAncestors(); i++) {
- sb.append(tt.getAncestor(i).getId());
- sb.append("/");
- }
- sb.append(tt.getId());
- return sb.toString();
- }
-
/**
* Discourage instantiation since this is a utility class.
*/
diff --git a/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java b/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java
index 5d0811c6..f9b37942 100644
--- a/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java
+++ b/src/main/java/org/apache/commons/scxml2/env/SimpleContext.java
@@ -75,26 +75,6 @@ public class SimpleContext implements Context, Serializable {
}
}
- /**
- * Assigns a new value to an existing variable or creates a new one.
- * The method searches the chain of parent Contexts for variable
- * existence.
- *
- * @param name The variable name
- * @param value The variable value
- * @see org.apache.commons.scxml2.Context#set(String, Object)
- */
- @Override
- public void set(final String name, final Object value) {
- if (getVars().containsKey(name)) { //first try to override local
- setLocal(name, value);
- } else if (parent != null && parent.has(name)) { //then check for global
- parent.set(name, value);
- } else { //otherwise create a new local variable
- setLocal(name, value);
- }
- }
-
/**
* Gets the value of this variable; delegating to parent.
*
@@ -114,6 +94,46 @@ public class SimpleContext implements Context, Serializable {
return null;
}
+ /**
+ * Gets the log used by this <code>Context</code> instance.
+ *
+ * @return Log The log being used.
+ */
+ protected Log getLog() {
+ return log;
+ }
+
+ /**
+ * Gets the parent Context, may be null.
+ *
+ * @return Context The parent Context
+ * @see org.apache.commons.scxml2.Context#getParent()
+ */
+ @Override
+ public Context getParent() {
+ return parent;
+ }
+
+ /**
+ * Gets the SCXMLSystemContext for this Context, should not be null unless this is the root Context
+ *
+ * @return The SCXMLSystemContext in a chained Context environment
+ */
+ @Override
+ public final SCXMLSystemContext getSystemContext() {
+ return systemContext;
+ }
+
+ /**
+ * Gets the Map of all local variables in this Context.
+ *
+ * @return Returns the vars.
+ */
+ @Override
+ public Map<String, Object> getVars() {
+ return vars;
+ }
+
/**
* Check if this variable exists, delegating to parent.
*
@@ -149,24 +169,23 @@ public class SimpleContext implements Context, Serializable {
}
/**
- * Gets the parent Context, may be null.
- *
- * @return Context The parent Context
- * @see org.apache.commons.scxml2.Context#getParent()
- */
- @Override
- public Context getParent() {
- return parent;
- }
-
- /**
- * Gets the SCXMLSystemContext for this Context, should not be null unless this is the root Context
+ * Assigns a new value to an existing variable or creates a new one.
+ * The method searches the chain of parent Contexts for variable
+ * existence.
*
- * @return The SCXMLSystemContext in a chained Context environment
+ * @param name The variable name
+ * @param value The variable value
+ * @see org.apache.commons.scxml2.Context#set(String, Object)
*/
@Override
- public final SCXMLSystemContext getSystemContext() {
- return systemContext;
+ public void set(final String name, final Object value) {
+ if (getVars().containsKey(name)) { //first try to override local
+ setLocal(name, value);
+ } else if (parent != null && parent.has(name)) { //then check for global
+ parent.set(name, value);
+ } else { //otherwise create a new local variable
+ setLocal(name, value);
+ }
}
/**
@@ -194,24 +213,5 @@ public class SimpleContext implements Context, Serializable {
protected void setVars(final Map<String, Object> vars) {
this.vars = vars;
}
-
- /**
- * Gets the Map of all local variables in this Context.
- *
- * @return Returns the vars.
- */
- @Override
- public Map<String, Object> getVars() {
- return vars;
- }
-
- /**
- * Gets the log used by this <code>Context</code> instance.
- *
- * @return Log The log being used.
- */
- protected Log getLog() {
- return log;
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java b/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java
index cd520564..ab4de809 100644
--- a/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java
+++ b/src/main/java/org/apache/commons/scxml2/env/SimpleDispatcher.java
@@ -46,10 +46,7 @@ import org.apache.commons.scxml2.model.ActionExecutionError;
*/
public class SimpleDispatcher implements EventDispatcher, Serializable {
- /** Serial version UID. */
- private static final long serialVersionUID = 1L;
-
- /**
+ /**
* TimerTask implementation.
*/
class DelayedEventTask extends TimerTask {
@@ -96,6 +93,9 @@ public class SimpleDispatcher implements EventDispatcher, Serializable {
}
}
+ /** Serial version UID. */
+ private static final long serialVersionUID = 1L;
+
/** Implementation independent log category. */
private static final Log log = LogFactory.getLog(EventDispatcher.class);
@@ -105,6 +105,28 @@ public class SimpleDispatcher implements EventDispatcher, Serializable {
*/
private final Map<String, Timer> timers = Collections.synchronizedMap(new HashMap<String, Timer>());
+ /**
+ * @see EventDispatcher#cancel(String)
+ */
+ @Override
+ public void cancel(final String sendId) {
+ if (log.isInfoEnabled()) {
+ log.info("cancel( sendId: " + sendId + ")");
+ }
+ if (!timers.containsKey(sendId)) {
+ return; // done, we don't track this one or its already expired
+ }
+ final Timer timer = timers.get(sendId);
+ if (timer != null) {
+ timer.cancel();
+ if (log.isDebugEnabled()) {
+ log.debug("Cancelled event scheduled by <send> with id '"
+ + sendId + "'");
+ }
+ }
+ timers.remove(sendId);
+ }
+
/**
* Gets the log instance.
*
@@ -114,6 +136,7 @@ public class SimpleDispatcher implements EventDispatcher, Serializable {
return log;
}
+
/**
* Gets the current timers.
*
@@ -123,34 +146,11 @@ public class SimpleDispatcher implements EventDispatcher, Serializable {
return timers;
}
-
@Override
public SimpleDispatcher newInstance() {
return new SimpleDispatcher();
}
- /**
- * @see EventDispatcher#cancel(String)
- */
- @Override
- public void cancel(final String sendId) {
- if (log.isInfoEnabled()) {
- log.info("cancel( sendId: " + sendId + ")");
- }
- if (!timers.containsKey(sendId)) {
- return; // done, we don't track this one or its already expired
- }
- final Timer timer = timers.get(sendId);
- if (timer != null) {
- timer.cancel();
- if (log.isDebugEnabled()) {
- log.debug("Cancelled event scheduled by <send> with id '"
- + sendId + "'");
- }
- }
- timers.remove(sendId);
- }
-
/**
@see EventDispatcher#send(java.util.Map, String, String, String, String, Object, Object, long)
*/
diff --git a/src/main/java/org/apache/commons/scxml2/env/SimpleErrorReporter.java b/src/main/java/org/apache/commons/scxml2/env/SimpleErrorReporter.java
index 648ef6d8..10988861 100644
--- a/src/main/java/org/apache/commons/scxml2/env/SimpleErrorReporter.java
+++ b/src/main/java/org/apache/commons/scxml2/env/SimpleErrorReporter.java
@@ -48,6 +48,28 @@ public class SimpleErrorReporter implements ErrorReporter, Serializable {
public SimpleErrorReporter() {
}
+ /**
+ * Final handling of the resulting errorMessage build by {@link #onError(String, String, Object)} onError}.
+ * <p>The default implementation write the errorMessage as a warning to the log.</p>
+ *
+ * @param errorCode
+ * one of the ErrorReporter's constants
+ * @param errDetail
+ * human readable description
+ * @param errCtx
+ * typically an SCXML element which caused an error,
+ * may be accompanied by additional information
+ * @param errorMessage
+ * human readable detail of the error including the state, transition and data
+ */
+ protected void handleErrorMessage(final String errorCode, final String errDetail,
+ final Object errCtx, final CharSequence errorMessage) {
+
+ if (log.isWarnEnabled()) {
+ log.warn(errorMessage.toString());
+ }
+ }
+
/**
* @see ErrorReporter#onError(String, String, Object)
*/
@@ -117,27 +139,5 @@ public class SimpleErrorReporter implements ErrorReporter, Serializable {
}
handleErrorMessage(errorCode, errDetail, errCtx, msg);
}
-
- /**
- * Final handling of the resulting errorMessage build by {@link #onError(String, String, Object)} onError}.
- * <p>The default implementation write the errorMessage as a warning to the log.</p>
- *
- * @param errorCode
- * one of the ErrorReporter's constants
- * @param errDetail
- * human readable description
- * @param errCtx
- * typically an SCXML element which caused an error,
- * may be accompanied by additional information
- * @param errorMessage
- * human readable detail of the error including the state, transition and data
- */
- protected void handleErrorMessage(final String errorCode, final String errDetail,
- final Object errCtx, final CharSequence errorMessage) {
-
- if (log.isWarnEnabled()) {
- log.warn(errorMessage.toString());
- }
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/Tracer.java b/src/main/java/org/apache/commons/scxml2/env/Tracer.java
index 24a38542..83b4ac5f 100644
--- a/src/main/java/org/apache/commons/scxml2/env/Tracer.java
+++ b/src/main/java/org/apache/commons/scxml2/env/Tracer.java
@@ -58,15 +58,6 @@ public class Tracer implements ErrorHandler, ErrorReporter,
xmlReporter = new SimpleXMLReporter();
}
- /**
- * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
- */
- @Override
- public void warning(final SAXParseException exception)
- throws SAXException {
- errHandler.warning(exception);
- }
-
/**
* @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
*/
@@ -86,20 +77,20 @@ public class Tracer implements ErrorHandler, ErrorReporter,
}
/**
- * @see ErrorReporter#onError(String, String, Object)
+ * @see SCXMLListener#onEntry(EnterableState)
*/
@Override
- public void onError(final String errCode, final String errDetail,
- final Object errCtx) {
- errReporter.onError(errCode, errDetail, errCtx);
+ public void onEntry(final EnterableState state) {
+ scxmlListener.onEntry(state);
}
/**
- * @see SCXMLListener#onEntry(EnterableState)
+ * @see ErrorReporter#onError(String, String, Object)
*/
@Override
- public void onEntry(final EnterableState state) {
- scxmlListener.onEntry(state);
+ public void onError(final String errCode, final String errDetail,
+ final Object errCtx) {
+ errReporter.onError(errCode, errDetail, errCtx);
}
/**
@@ -129,5 +120,14 @@ public class Tracer implements ErrorHandler, ErrorReporter,
xmlReporter.report(message, errorType, relatedInformation, location);
}
+ /**
+ * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
+ */
+ @Override
+ public void warning(final SAXParseException exception)
+ throws SAXException {
+ errHandler.warning(exception);
+ }
+
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/URLResolver.java b/src/main/java/org/apache/commons/scxml2/env/URLResolver.java
index d3f62051..20edf3c3 100644
--- a/src/main/java/org/apache/commons/scxml2/env/URLResolver.java
+++ b/src/main/java/org/apache/commons/scxml2/env/URLResolver.java
@@ -50,15 +50,14 @@ public class URLResolver implements PathResolver, Serializable {
}
/**
- * Uses URL(URL, String) constructor to combine URL's.
- * @see org.apache.commons.scxml2.PathResolver#resolvePath(String)
+ * @see org.apache.commons.scxml2.PathResolver#getResolver(String)
*/
@Override
- public String resolvePath(final String ctxPath) {
+ public PathResolver getResolver(final String ctxPath) {
URL combined;
try {
combined = new URL(baseURL, ctxPath);
- return combined.toString();
+ return new URLResolver(combined);
} catch (final MalformedURLException e) {
log.error("Malformed URL", e);
}
@@ -66,14 +65,15 @@ public class URLResolver implements PathResolver, Serializable {
}
/**
- * @see org.apache.commons.scxml2.PathResolver#getResolver(String)
+ * Uses URL(URL, String) constructor to combine URL's.
+ * @see org.apache.commons.scxml2.PathResolver#resolvePath(String)
*/
@Override
- public PathResolver getResolver(final String ctxPath) {
+ public String resolvePath(final String ctxPath) {
URL combined;
try {
combined = new URL(baseURL, ctxPath);
- return new URLResolver(combined);
+ return combined.toString();
} catch (final MalformedURLException e) {
log.error("Malformed URL", e);
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java
index d5191b4f..ae1fb85c 100644
--- a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java
+++ b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContext.java
@@ -44,13 +44,6 @@ public class GroovyContext extends SimpleContext {
private GroovyContextBinding binding;
private Map<String, Object> vars;
- GroovyContextBinding getBinding() {
- if (binding == null) {
- binding = new GroovyContextBinding(this);
- }
- return binding;
- }
-
/**
* Constructor.
*/
@@ -58,34 +51,47 @@ public class GroovyContext extends SimpleContext {
}
/**
- * Constructor with initial vars.
+ * Constructor with parent context.
*
* @param parent The parent context.
- * @param initialVars The initial set of variables.
* @param evaluator The groovy evaluator
*/
- public GroovyContext(final Context parent, final Map<String, Object> initialVars, final GroovyEvaluator evaluator) {
- super(parent, initialVars);
+ public GroovyContext(final Context parent, final GroovyEvaluator evaluator) {
+ super(parent);
this.evaluator = evaluator;
}
/**
- * Constructor with parent context.
+ * Constructor with initial vars.
*
* @param parent The parent context.
+ * @param initialVars The initial set of variables.
* @param evaluator The groovy evaluator
*/
- public GroovyContext(final Context parent, final GroovyEvaluator evaluator) {
- super(parent);
+ public GroovyContext(final Context parent, final Map<String, Object> initialVars, final GroovyEvaluator evaluator) {
+ super(parent, initialVars);
this.evaluator = evaluator;
}
+ GroovyContextBinding getBinding() {
+ if (binding == null) {
+ binding = new GroovyContextBinding(this);
+ }
+ return binding;
+ }
+
protected GroovyEvaluator getGroovyEvaluator() {
return evaluator;
}
- protected void setGroovyEvaluator(final GroovyEvaluator evaluator) {
- this.evaluator = evaluator;
+ protected String getScriptBaseClass() {
+ if (scriptBaseClass != null) {
+ return scriptBaseClass;
+ }
+ if (getParent() instanceof GroovyContext) {
+ return ((GroovyContext)getParent()).getScriptBaseClass();
+ }
+ return null;
}
@Override
@@ -93,23 +99,36 @@ public class GroovyContext extends SimpleContext {
return vars;
}
- @Override
- protected void setVars(final Map<String, Object> vars) {
- this.vars = vars;
+ @SuppressWarnings("unchecked")
+ private void readObject(final ObjectInputStream in) throws IOException,ClassNotFoundException {
+ this.scriptBaseClass = (String)in.readObject();
+ this.evaluator = (GroovyEvaluator)in.readObject();
+ this.binding = (GroovyContextBinding)in.readObject();
+ SCInstanceObjectInputStream.ClassResolver currentResolver = null;
+ try {
+ if (evaluator != null && in instanceof SCInstanceObjectInputStream) {
+ currentResolver = ((SCInstanceObjectInputStream)in).setClassResolver(osc -> Class.forName(osc.getName(), true, evaluator.getGroovyClassLoader()));
+ }
+ this.vars = (Map<String, Object>)in.readObject();
+ }
+ finally {
+ if (in instanceof SCInstanceObjectInputStream) {
+ ((SCInstanceObjectInputStream)in).setClassResolver(currentResolver);
+ }
+ }
+ }
+
+ protected void setGroovyEvaluator(final GroovyEvaluator evaluator) {
+ this.evaluator = evaluator;
}
protected void setScriptBaseClass(final String scriptBaseClass) {
this.scriptBaseClass = scriptBaseClass;
}
- protected String getScriptBaseClass() {
- if (scriptBaseClass != null) {
- return scriptBaseClass;
- }
- if (getParent() instanceof GroovyContext) {
- return ((GroovyContext)getParent()).getScriptBaseClass();
- }
- return null;
+ @Override
+ protected void setVars(final Map<String, Object> vars) {
+ this.vars = vars;
}
private void writeObject(final ObjectOutputStream out) throws IOException {
@@ -132,23 +151,4 @@ public class GroovyContext extends SimpleContext {
out.writeObject(this.binding);
out.writeObject(this.vars);
}
-
- @SuppressWarnings("unchecked")
- private void readObject(final ObjectInputStream in) throws IOException,ClassNotFoundException {
- this.scriptBaseClass = (String)in.readObject();
- this.evaluator = (GroovyEvaluator)in.readObject();
- this.binding = (GroovyContextBinding)in.readObject();
- SCInstanceObjectInputStream.ClassResolver currentResolver = null;
- try {
- if (evaluator != null && in instanceof SCInstanceObjectInputStream) {
- currentResolver = ((SCInstanceObjectInputStream)in).setClassResolver(osc -> Class.forName(osc.getName(), true, evaluator.getGroovyClassLoader()));
- }
- this.vars = (Map<String, Object>)in.readObject();
- }
- finally {
- if (in instanceof SCInstanceObjectInputStream) {
- ((SCInstanceObjectInputStream)in).setClassResolver(currentResolver);
- }
- }
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContextBinding.java b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContextBinding.java
index 79b19785..74ebff5a 100644
--- a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContextBinding.java
+++ b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyContextBinding.java
@@ -43,6 +43,11 @@ public class GroovyContextBinding extends Binding implements Serializable {
return context;
}
+ @Override
+ public Object getProperty(final String property) {
+ return getVariable(property);
+ }
+
@Override
public Object getVariable(final String name) {
final Object result = context.get(name);
@@ -53,12 +58,8 @@ public class GroovyContextBinding extends Binding implements Serializable {
}
@Override
- public void setVariable(final String name, final Object value) {
- if (context.has(name)) {
- context.set(name, value);
- } else {
- context.setLocal(name, value);
- }
+ public Map<String, Object> getVariables() {
+ return new LinkedHashMap<>(context.getVars());
}
@Override
@@ -67,17 +68,16 @@ public class GroovyContextBinding extends Binding implements Serializable {
}
@Override
- public Map<String, Object> getVariables() {
- return new LinkedHashMap<>(context.getVars());
- }
-
- @Override
- public Object getProperty(final String property) {
- return getVariable(property);
+ public void setProperty(final String property, final Object newValue) {
+ setVariable(property, newValue);
}
@Override
- public void setProperty(final String property, final Object newValue) {
- setVariable(property, newValue);
+ public void setVariable(final String name, final Object value) {
+ if (context.has(name)) {
+ context.set(name, value);
+ } else {
+ context.setLocal(name, value);
+ }
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java
index 897ba124..5194d969 100644
--- a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyEvaluator.java
@@ -41,18 +41,8 @@ import org.apache.commons.scxml2.model.SCXML;
*/
public class GroovyEvaluator extends AbstractBaseEvaluator {
- /** Serial version UID. */
- private static final long serialVersionUID = 1L;
-
- public static final String SUPPORTED_DATA_MODEL = "groovy";
-
public static class GroovyEvaluatorProvider implements EvaluatorProvider {
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
@Override
public Evaluator getEvaluator() {
return new GroovyEvaluator();
@@ -62,8 +52,18 @@ public class GroovyEvaluator extends AbstractBaseEvaluator {
public Evaluator getEvaluator(final SCXML document) {
return new GroovyEvaluator();
}
+
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
}
+ /** Serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ public static final String SUPPORTED_DATA_MODEL = "groovy";
+
/** Error message if evaluation context is not a GroovyContext. */
private static final String ERR_CTX_TYPE = "Error evaluating Groovy "
+ "expression, Context must be a org.apache.commons.scxml2.env.groovy.GroovyContext";
@@ -117,51 +117,11 @@ public class GroovyEvaluator extends AbstractBaseEvaluator {
this.scriptCache = newScriptCache();
}
- /**
- * Overridable factory method to create the GroovyExtendableScriptCache for this GroovyEvaluator.
- * <p>
- * The default implementation configures the scriptCache to use the {@link #scriptPreProcessor GroovyEvaluator scriptPreProcessor}
- * and the {@link GroovySCXMLScript} as script base class.
- * </p>
- *
- * @return GroovyExtendableScriptCache for this GroovyEvaluator
- */
- protected GroovyExtendableScriptCache newScriptCache() {
- final GroovyExtendableScriptCache scriptCache = new GroovyExtendableScriptCache();
- scriptCache.setScriptPreProcessor(getScriptPreProcessor());
- scriptCache.setScriptBaseClass(GroovySCXMLScript.class.getName());
- return scriptCache;
- }
-
- @SuppressWarnings("unchecked")
- protected Script getScript(final GroovyContext groovyContext, final String scriptBaseClassName, final String scriptSource) {
- final Script script = scriptCache.getScript(scriptBaseClassName, scriptSource);
- script.setBinding(groovyContext.getBinding());
- return script;
- }
-
@SuppressWarnings("unused")
public void clearCache() {
scriptCache.clearCache();
}
- public GroovyExtendableScriptCache.ScriptPreProcessor getScriptPreProcessor() {
- return scriptPreProcessor;
- }
-
- /* SCXMLEvaluator implementation methods */
-
-
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
- @Override
- public boolean requiresGlobalContext() {
- return false;
- }
-
/**
* Evaluate an expression.
*
@@ -252,10 +212,42 @@ public class GroovyEvaluator extends AbstractBaseEvaluator {
}
}
+ /* SCXMLEvaluator implementation methods */
+
+
+ /**
+ * Create a new context which is the summation of contexts from the
+ * current state to document root, child has priority over parent
+ * in scoping rules.
+ *
+ * @param nodeCtx The GroovyContext for this state.
+ * @return The effective GroovyContext for the path leading up to
+ * document root.
+ */
+ protected GroovyContext getEffectiveContext(final GroovyContext nodeCtx) {
+ return new GroovyContext(nodeCtx, new EffectiveContextMap(nodeCtx), this);
+ }
+
protected ClassLoader getGroovyClassLoader() {
return scriptCache.getGroovyClassLoader();
}
+ @SuppressWarnings("unchecked")
+ protected Script getScript(final GroovyContext groovyContext, final String scriptBaseClassName, final String scriptSource) {
+ final Script script = scriptCache.getScript(scriptBaseClassName, scriptSource);
+ script.setBinding(groovyContext.getBinding());
+ return script;
+ }
+
+ public GroovyExtendableScriptCache.ScriptPreProcessor getScriptPreProcessor() {
+ return scriptPreProcessor;
+ }
+
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
+
/**
* Create a new child context.
*
@@ -269,15 +261,23 @@ public class GroovyEvaluator extends AbstractBaseEvaluator {
}
/**
- * Create a new context which is the summation of contexts from the
- * current state to document root, child has priority over parent
- * in scoping rules.
+ * Overridable factory method to create the GroovyExtendableScriptCache for this GroovyEvaluator.
+ * <p>
+ * The default implementation configures the scriptCache to use the {@link #scriptPreProcessor GroovyEvaluator scriptPreProcessor}
+ * and the {@link GroovySCXMLScript} as script base class.
+ * </p>
*
- * @param nodeCtx The GroovyContext for this state.
- * @return The effective GroovyContext for the path leading up to
- * document root.
+ * @return GroovyExtendableScriptCache for this GroovyEvaluator
*/
- protected GroovyContext getEffectiveContext(final GroovyContext nodeCtx) {
- return new GroovyContext(nodeCtx, new EffectiveContextMap(nodeCtx), this);
+ protected GroovyExtendableScriptCache newScriptCache() {
+ final GroovyExtendableScriptCache scriptCache = new GroovyExtendableScriptCache();
+ scriptCache.setScriptPreProcessor(getScriptPreProcessor());
+ scriptCache.setScriptBaseClass(GroovySCXMLScript.class.getName());
+ return scriptCache;
+ }
+
+ @Override
+ public boolean requiresGlobalContext() {
+ return false;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyExtendableScriptCache.java b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyExtendableScriptCache.java
index 3b5d983c..3d135f38 100644
--- a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyExtendableScriptCache.java
+++ b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovyExtendableScriptCache.java
@@ -70,16 +70,6 @@ import org.codehaus.groovy.control.CompilerConfiguration;
*/
public class GroovyExtendableScriptCache implements Serializable {
- private static final long serialVersionUID = 1L;
-
- /**
- * Serializable factory interface providing the Groovy parent ClassLoader,
- * needed to restore the specific ClassLoader after de-serialization
- */
- public interface ParentClassLoaderFactory extends Serializable {
- ClassLoader getClassLoader();
- }
-
/**
* Serializable factory interface providing the Groovy CompilerConfiguration,
* needed to restore the specific CompilerConfiguration after de-serialization
@@ -88,21 +78,14 @@ public class GroovyExtendableScriptCache implements Serializable {
CompilerConfiguration getCompilerConfiguration();
}
- public interface ScriptPreProcessor extends Serializable {
- String preProcess(String script);
+ /**
+ * Serializable factory interface providing the Groovy parent ClassLoader,
+ * needed to restore the specific ClassLoader after de-serialization
+ */
+ public interface ParentClassLoaderFactory extends Serializable {
+ ClassLoader getClassLoader();
}
- /** Default CodeSource code base for the compiled Groovy scripts */
- public static final String DEFAULT_SCRIPT_CODE_BASE = "/groovy/scxml/script";
-
- /** Default factory for the Groovy parent ClassLoader, returning this class its ClassLoader */
- public static final ParentClassLoaderFactory DEFAULT_PARENT_CLASS_LOADER_FACTORY =
- (ParentClassLoaderFactory) GroovyExtendableScriptCache.class::getClassLoader;
-
- /** Default factory for the Groovy CompilerConfiguration, returning a new and unmodified CompilerConfiguration instance */
- public static final CompilerConfigurationFactory DEFAULT_COMPILER_CONFIGURATION_FACTORY =
- (CompilerConfigurationFactory) CompilerConfiguration::new;
-
protected static class ScriptCacheElement implements Serializable {
private static final long serialVersionUID = 1L;
@@ -116,30 +99,6 @@ public class GroovyExtendableScriptCache implements Serializable {
this.scriptSource = scriptSource;
}
- public String getBaseClass() {
- return baseClass;
- }
-
- public String getScriptSource() {
- return scriptSource;
- }
-
- public String getScriptName() {
- return scriptName;
- }
-
- public void setScriptName(final String scriptName) {
- this.scriptName = scriptName;
- }
-
- public Class<? extends Script> getScriptClass() {
- return scriptClass;
- }
-
- public void setScriptClass(final Class<? extends Script> scriptClass) {
- this.scriptClass = scriptClass;
- }
-
@Override
public boolean equals(final Object o) {
if (this == o) {
@@ -156,14 +115,55 @@ public class GroovyExtendableScriptCache implements Serializable {
}
+ public String getBaseClass() {
+ return baseClass;
+ }
+
+ public Class<? extends Script> getScriptClass() {
+ return scriptClass;
+ }
+
+ public String getScriptName() {
+ return scriptName;
+ }
+
+ public String getScriptSource() {
+ return scriptSource;
+ }
+
@Override
public int hashCode() {
int result = baseClass != null ? baseClass.hashCode() : 0;
result = 31 * result + scriptSource.hashCode();
return result;
}
+
+ public void setScriptClass(final Class<? extends Script> scriptClass) {
+ this.scriptClass = scriptClass;
+ }
+
+ public void setScriptName(final String scriptName) {
+ this.scriptName = scriptName;
+ }
+ }
+
+ public interface ScriptPreProcessor extends Serializable {
+ String preProcess(String script);
}
+ private static final long serialVersionUID = 1L;
+
+ /** Default CodeSource code base for the compiled Groovy scripts */
+ public static final String DEFAULT_SCRIPT_CODE_BASE = "/groovy/scxml/script";
+
+ /** Default factory for the Groovy parent ClassLoader, returning this class its ClassLoader */
+ public static final ParentClassLoaderFactory DEFAULT_PARENT_CLASS_LOADER_FACTORY =
+ (ParentClassLoaderFactory) GroovyExtendableScriptCache.class::getClassLoader;
+
+ /** Default factory for the Groovy CompilerConfiguration, returning a new and unmodified CompilerConfiguration instance */
+ public static final CompilerConfigurationFactory DEFAULT_COMPILER_CONFIGURATION_FACTORY =
+ (CompilerConfigurationFactory) CompilerConfiguration::new;
+
private final LinkedHashMap<ScriptCacheElement, ScriptCacheElement> scriptCache = new LinkedHashMap<>();
private String scriptCodeBase = DEFAULT_SCRIPT_CODE_BASE;
@@ -179,19 +179,68 @@ public class GroovyExtendableScriptCache implements Serializable {
public GroovyExtendableScriptCache() {
}
- /*
- * Hook into the de-serialization process, reloading the transient GroovyClassLoader, CompilerConfiguration and
- * re-generate Script classes through {@link #ensureInitializedOrReloaded()}
- */
- private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- ensureInitializedOrReloaded();
+ public void clearCache() {
+ synchronized (scriptCache) {
+ scriptCache.clear();
+ if (groovyClassLoader != null) {
+ groovyClassLoader.clearCache();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Class<Script> compileScript(final String scriptBaseClass, final String scriptSource, final String scriptName) {
+ final String script = preProcessScript(scriptSource);
+
+ final GroovyCodeSource codeSource = AccessController.doPrivileged((PrivilegedAction<GroovyCodeSource>)
+ () -> new GroovyCodeSource(script, scriptName, getScriptCodeBase()));
+
+ final String currentScriptBaseClass = compilerConfiguration.getScriptBaseClass();
+ try {
+ if (scriptBaseClass != null) {
+ compilerConfiguration.setScriptBaseClass(scriptBaseClass);
+ }
+ return groovyClassLoader.parseClass(codeSource, false);
+ }
+ finally {
+ compilerConfiguration.setScriptBaseClass(currentScriptBaseClass);
+ }
+ }
+
+ protected void ensureInitializedOrReloaded() {
+ if (groovyClassLoader == null) {
+ compilerConfiguration = new CompilerConfiguration(getCompilerConfigurationFactory().getCompilerConfiguration());
+ if (getScriptBaseClass() != null) {
+ compilerConfiguration.setScriptBaseClass(getScriptBaseClass());
+ }
+
+ groovyClassLoader = AccessController.doPrivileged((PrivilegedAction<GroovyClassLoader>)
+ () -> new GroovyClassLoader(getParentClassLoaderFactory().getClassLoader(), compilerConfiguration));
+ if (!scriptCache.isEmpty()) {
+ // de-serialized: need to re-generate all previously compiled scripts (this can cause a hick-up...):
+ for (final ScriptCacheElement element : scriptCache.keySet()) {
+ element.setScriptClass(compileScript(element.getBaseClass(), element.getScriptSource(), element.getScriptName()));
+ }
+ }
+ }
+ }
+
+ protected String generatedScriptName(final String scriptSource, final int seed) {
+ return "script"+seed+"_"+Math.abs(scriptSource.hashCode())+".groovy";
+ }
+
+ public CompilerConfigurationFactory getCompilerConfigurationFactory() {
+ return compilerConfigurationFactory;
}
public ClassLoader getGroovyClassLoader() {
return groovyClassLoader;
}
+ public ParentClassLoaderFactory getParentClassLoaderFactory() {
+ return parentClassLoaderFactory;
+ }
+
/**
* @param scriptSource The script source, which will optionally be first preprocessed through {@link #preProcessScript(String)}
* using the configured {@link #getScriptPreProcessor}
@@ -226,40 +275,22 @@ public class GroovyExtendableScriptCache implements Serializable {
}
}
- protected void ensureInitializedOrReloaded() {
- if (groovyClassLoader == null) {
- compilerConfiguration = new CompilerConfiguration(getCompilerConfigurationFactory().getCompilerConfiguration());
- if (getScriptBaseClass() != null) {
- compilerConfiguration.setScriptBaseClass(getScriptBaseClass());
- }
-
- groovyClassLoader = AccessController.doPrivileged((PrivilegedAction<GroovyClassLoader>)
- () -> new GroovyClassLoader(getParentClassLoaderFactory().getClassLoader(), compilerConfiguration));
- if (!scriptCache.isEmpty()) {
- // de-serialized: need to re-generate all previously compiled scripts (this can cause a hick-up...):
- for (final ScriptCacheElement element : scriptCache.keySet()) {
- element.setScriptClass(compileScript(element.getBaseClass(), element.getScriptSource(), element.getScriptName()));
- }
- }
- }
+ public String getScriptBaseClass() {
+ return scriptBaseClass;
}
- @SuppressWarnings("unchecked")
- protected Class<Script> compileScript(final String scriptBaseClass, final String scriptSource, final String scriptName) {
- final String script = preProcessScript(scriptSource);
+ /** @return The current configured CodeSource code base used for the compilation of the Groovy scripts */
+ public String getScriptCodeBase() {
+ return scriptCodeBase;
+ }
- final GroovyCodeSource codeSource = AccessController.doPrivileged((PrivilegedAction<GroovyCodeSource>)
- () -> new GroovyCodeSource(script, scriptName, getScriptCodeBase()));
+ public ScriptPreProcessor getScriptPreProcessor() {
+ return scriptPreProcessor;
+ }
- final String currentScriptBaseClass = compilerConfiguration.getScriptBaseClass();
- try {
- if (scriptBaseClass != null) {
- compilerConfiguration.setScriptBaseClass(scriptBaseClass);
- }
- return groovyClassLoader.parseClass(codeSource, false);
- }
- finally {
- compilerConfiguration.setScriptBaseClass(currentScriptBaseClass);
+ public boolean isEmpty() {
+ synchronized (scriptCache) {
+ return scriptCache.isEmpty();
}
}
@@ -267,13 +298,27 @@ public class GroovyExtendableScriptCache implements Serializable {
return getScriptPreProcessor() != null ? getScriptPreProcessor().preProcess(scriptSource) : scriptSource;
}
- protected String generatedScriptName(final String scriptSource, final int seed) {
- return "script"+seed+"_"+Math.abs(scriptSource.hashCode())+".groovy";
+ /*
+ * Hook into the de-serialization process, reloading the transient GroovyClassLoader, CompilerConfiguration and
+ * re-generate Script classes through {@link #ensureInitializedOrReloaded()}
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ ensureInitializedOrReloaded();
}
- /** @return The current configured CodeSource code base used for the compilation of the Groovy scripts */
- public String getScriptCodeBase() {
- return scriptCodeBase;
+ @SuppressWarnings("unused")
+ public void setCompilerConfigurationFactory(final CompilerConfigurationFactory compilerConfigurationFactory) {
+ this.compilerConfigurationFactory = compilerConfigurationFactory != null ? compilerConfigurationFactory : DEFAULT_COMPILER_CONFIGURATION_FACTORY;
+ }
+
+ @SuppressWarnings("unused")
+ public void setParentClassLoaderFactory(final ParentClassLoaderFactory parentClassLoaderFactory) {
+ this.parentClassLoaderFactory = parentClassLoaderFactory != null ? parentClassLoaderFactory : DEFAULT_PARENT_CLASS_LOADER_FACTORY;
+ }
+
+ public void setScriptBaseClass(final String scriptBaseClass) {
+ this.scriptBaseClass = scriptBaseClass;
}
/**
@@ -290,52 +335,7 @@ public class GroovyExtendableScriptCache implements Serializable {
this.scriptCodeBase = DEFAULT_SCRIPT_CODE_BASE;
}
}
-
- public String getScriptBaseClass() {
- return scriptBaseClass;
- }
-
- public void setScriptBaseClass(final String scriptBaseClass) {
- this.scriptBaseClass = scriptBaseClass;
- }
-
- public ParentClassLoaderFactory getParentClassLoaderFactory() {
- return parentClassLoaderFactory;
- }
-
- @SuppressWarnings("unused")
- public void setParentClassLoaderFactory(final ParentClassLoaderFactory parentClassLoaderFactory) {
- this.parentClassLoaderFactory = parentClassLoaderFactory != null ? parentClassLoaderFactory : DEFAULT_PARENT_CLASS_LOADER_FACTORY;
- }
-
- public CompilerConfigurationFactory getCompilerConfigurationFactory() {
- return compilerConfigurationFactory;
- }
-
- @SuppressWarnings("unused")
- public void setCompilerConfigurationFactory(final CompilerConfigurationFactory compilerConfigurationFactory) {
- this.compilerConfigurationFactory = compilerConfigurationFactory != null ? compilerConfigurationFactory : DEFAULT_COMPILER_CONFIGURATION_FACTORY;
- }
-
- public ScriptPreProcessor getScriptPreProcessor() {
- return scriptPreProcessor;
- }
-
public void setScriptPreProcessor(final ScriptPreProcessor scriptPreProcessor) {
this.scriptPreProcessor = scriptPreProcessor;
}
-
- public boolean isEmpty() {
- synchronized (scriptCache) {
- return scriptCache.isEmpty();
- }
- }
- public void clearCache() {
- synchronized (scriptCache) {
- scriptCache.clear();
- if (groovyClassLoader != null) {
- groovyClassLoader.clearCache();
- }
- }
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java
index fc862916..c0723fcb 100644
--- a/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java
+++ b/src/main/java/org/apache/commons/scxml2/env/groovy/GroovySCXMLScript.java
@@ -38,10 +38,30 @@ public abstract class GroovySCXMLScript extends Script {
super(null);
}
- @Override
- public void setBinding(final Binding binding) {
- super.setBinding(binding);
- this.context = ((GroovyContextBinding) binding).getContext();
+ /**
+ * The empty function mimics the behavior of the JEXL empty function, in that it returns true if the parameter is:
+ * <ul>
+ * <li>null, or</li>
+ * <li>an empty String, or</li>
+ * <li>an zero length Array, or</li>
+ * <li>an empty Collection, or</li>
+ * <li>an empty Map</li>
+ * </ul>
+ * <p>
+ * Note: one difference with the JEXL language is that Groovy doesn't allow checking for undefined variables.<br>
+ * Before being able to check, Groovy will already have raised an MissingPropertyException if the variable cannot be found.<br>
+ * To work around this, the custom {@link #var(String)} function is available.
+ * </p>
+ *
+ * @param obj the object to check if it is empty
+ * @return true if the object is empty, false otherwise
+ */
+ public boolean empty(final Object obj) {
+ return obj == null ||
+ (obj instanceof String && ((String)obj).isEmpty()) ||
+ ((obj.getClass().isArray() && Array.getLength(obj)==0)) ||
+ (obj instanceof Collection && ((Collection)obj).size()==0) ||
+ (obj instanceof Map && ((Map)obj).isEmpty());
}
/**
@@ -53,6 +73,12 @@ public abstract class GroovySCXMLScript extends Script {
return Builtin.isMember(context, state);
}
+ @Override
+ public void setBinding(final Binding binding) {
+ super.setBinding(binding);
+ this.context = ((GroovyContextBinding) binding).getContext();
+ }
+
/**
* The var function can be used to check if a variable is defined,
* <p>
@@ -85,30 +111,4 @@ public abstract class GroovySCXMLScript extends Script {
}
return true;
}
-
- /**
- * The empty function mimics the behavior of the JEXL empty function, in that it returns true if the parameter is:
- * <ul>
- * <li>null, or</li>
- * <li>an empty String, or</li>
- * <li>an zero length Array, or</li>
- * <li>an empty Collection, or</li>
- * <li>an empty Map</li>
- * </ul>
- * <p>
- * Note: one difference with the JEXL language is that Groovy doesn't allow checking for undefined variables.<br>
- * Before being able to check, Groovy will already have raised an MissingPropertyException if the variable cannot be found.<br>
- * To work around this, the custom {@link #var(String)} function is available.
- * </p>
- *
- * @param obj the object to check if it is empty
- * @return true if the object is empty, false otherwise
- */
- public boolean empty(final Object obj) {
- return obj == null ||
- (obj instanceof String && ((String)obj).isEmpty()) ||
- ((obj.getClass().isArray() && Array.getLength(obj)==0)) ||
- (obj instanceof Collection && ((Collection)obj).size()==0) ||
- (obj instanceof Map && ((Map)obj).isEmpty());
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java
index 6e520010..ff01077e 100644
--- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java
+++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSBindings.java
@@ -45,16 +45,10 @@ public class JSBindings implements Bindings {
}
/**
- * Sets or update the SCXML context delegate
- *
- * @param jsContext the SCXML context to use for script variables.
- * @throws IllegalArgumentException Thrown if <code>jsContext</code> is <code>null</code>.
+ * Does nothing - never invoked anyway
*/
- public void setContext(final JSContext jsContext) {
- if (jsContext == null) {
- throw new IllegalArgumentException("SCXML context is required");
- }
- this.context = jsContext;
+ @Override
+ public void clear() {
}
/**
@@ -65,22 +59,6 @@ public class JSBindings implements Bindings {
return context.has(key.toString());
}
- /**
- * Returns the SCXML context key set
- */
- @Override
- public Set<String> keySet() {
- return context.getVars().keySet();
- }
-
- /**
- * Returns the size of the SCXML context size.
- */
- @Override
- public int size() {
- return context.getVars().size();
- }
-
/**
* Returns <code>true</code> if the SCXML context contains <code>value</code>.
*/
@@ -98,11 +76,11 @@ public class JSBindings implements Bindings {
}
/**
- * Returns the SCXML context values.
+ * Returns the value from the SCXML context identified by <code>key</code>.
*/
@Override
- public Collection<Object> values() {
- return context.getVars().values();
+ public Object get(final Object key) {
+ return context.get(key.toString());
}
/**
@@ -114,11 +92,11 @@ public class JSBindings implements Bindings {
}
/**
- * Returns the value from the SCXML context identified by <code>key</code>.
+ * Returns the SCXML context key set
*/
@Override
- public Object get(final Object key) {
- return context.get(key.toString());
+ public Set<String> keySet() {
+ return context.getVars().keySet();
}
/**
@@ -163,9 +141,31 @@ public class JSBindings implements Bindings {
}
/**
- * Does nothing - never invoked anyway
+ * Sets or update the SCXML context delegate
+ *
+ * @param jsContext the SCXML context to use for script variables.
+ * @throws IllegalArgumentException Thrown if <code>jsContext</code> is <code>null</code>.
+ */
+ public void setContext(final JSContext jsContext) {
+ if (jsContext == null) {
+ throw new IllegalArgumentException("SCXML context is required");
+ }
+ this.context = jsContext;
+ }
+
+ /**
+ * Returns the size of the SCXML context size.
*/
@Override
- public void clear() {
+ public int size() {
+ return context.getVars().size();
+ }
+
+ /**
+ * Returns the SCXML context values.
+ */
+ @Override
+ public Collection<Object> values() {
+ return context.getVars().values();
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java
index fb605b77..50e9d177 100644
--- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java
+++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSContext.java
@@ -38,6 +38,14 @@ public class JSContext extends SimpleContext {
public JSContext() {
}
+ /**
+ * Child constructor - Just invokes the identical SimpleContext constructor.
+ * @param parent Parent context for this context.
+ */
+ public JSContext(final Context parent) {
+ super(parent);
+ }
+
/**
* Constructor with initial vars - Just invokes the identical SimpleContext constructor.
* @param parent The parent context
@@ -46,13 +54,5 @@ public class JSContext extends SimpleContext {
public JSContext(final Context parent, final Map<String, Object> initialVars) {
super(parent, initialVars);
}
-
- /**
- * Child constructor - Just invokes the identical SimpleContext constructor.
- * @param parent Parent context for this context.
- */
- public JSContext(final Context parent) {
- super(parent);
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java
index 71730276..6cc982bf 100644
--- a/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/javascript/JSEvaluator.java
@@ -55,15 +55,8 @@ import org.apache.commons.scxml2.model.SCXML;
*/
public class JSEvaluator extends AbstractBaseEvaluator {
- public static final String SUPPORTED_DATA_MODEL = Evaluator.ECMASCRIPT_DATA_MODEL;
-
public static class JSEvaluatorProvider implements EvaluatorProvider {
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
@Override
public Evaluator getEvaluator() {
return new JSEvaluator();
@@ -73,130 +66,48 @@ public class JSEvaluator extends AbstractBaseEvaluator {
public Evaluator getEvaluator(final SCXML document) {
return new JSEvaluator();
}
+
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
}
+ public static final String SUPPORTED_DATA_MODEL = Evaluator.ECMASCRIPT_DATA_MODEL;
+
private static final String SCXML_SYSTEM_CONTEXT = "_scxmlSystemContext";
/** Error message if evaluation context is not a JexlContext. */
private static final String ERR_CTX_TYPE = "Error evaluating JavaScript "
+ "expression, Context must be a org.apache.commons.scxml2.env.javascript.JSContext";
- /** Nashorn ScriptEngine **/
- private transient ScriptEngine engine;
-
/** Nashorn Global initialization script, loaded from <code>init_global.js</code> classpath resource */
private static String initGlobalsScript;
+ /** Nashorn ScriptEngine **/
+ private transient ScriptEngine engine;
+
/** ScriptContext for a single SCXML instance (JSEvaluator also cannot be shared between SCXML instances) */
private transient ScriptContext scriptContext;
/**
- * Initialize the singleton Javascript ScriptEngine to be used with a separate ScriptContext for each SCXML instance
- * not sharing their global scope, see {@link #getScriptContext(JSContext)}.
- * <p>
- * The SCXML required protected system variables and (possible) other Javascript global initializations are defined
- * in a <code>init_global.js</code> script which is pre-loaded as (classpath) resource, to be executed once during
- * initialization of a new Javascript (Nashorn) Global.
- * </p>
+ * Copy the Javscript global context (i.e. nashorn Global instance) variables to SCXML {@code jsContext}
+ * in order to make sure all the new global variables set by the JavaScript engine after evaluation are
+ * available from {@link JSContext} instance as well.
+ * <p>Note: the internal <code>"_scxmlSystemContext</code> variable is always skipped.</p>
+ * @param global The Javascript Bindings holding the Javascript Global context variables
+ * @param jsContext The SCXML context to copy/merge the variables into
*/
- protected synchronized void initEngine() {
- if (engine == null) {
- engine = new ScriptEngineManager().getEngineByName("JavaScript");
- if (initGlobalsScript == null) {
- try {
- initGlobalsScript = IOUtils.toString(JSEvaluator.class.getResourceAsStream("init_global.js"), "UTF-8");
- }
- catch (final IOException ioe) {
- throw new UncheckedIOException("Failed to load init_global.js from classpath", ioe);
+ private void copyJavascriptGlobalsToScxmlContext(final Bindings global, final JSContext jsContext) {
+ if (global != null) {
+ for (final String key : global.keySet()) {
+ if (!SCXML_SYSTEM_CONTEXT.equals(key)) {
+ jsContext.set(key, global.get(key));
}
}
}
}
- /**
- * Gets the singleton ScriptEngine, initializing it on first access
- * @return The ScriptEngine
- */
- protected ScriptEngine getEngine() {
- if (engine == null) {
- initEngine();
- }
- return engine;
- }
-
- /**
- * Gets the current ScriptContext or create a new one.
- * <p>
- * The ScriptContext is (to be) shared across invocations for the same SCXML instance as it holds the Javascript 'global'
- * context.
- * </p>
- * <p>
- * The ScriptContext is using a {@link ScriptContext#ENGINE_SCOPE} as provided by the engine, which in case of Nashorn
- * is bound to the Javscript global context. Note: do <em>not</em> confuse this with the {@link ScriptContext#GLOBAL_SCOPE} binding.
- * </p>
- * <p>For a newly created ScriptContext (and thus a new Javascript global context), the Javascript global context is
- * initialized with the required and protected SCXML system variables and builtin In() operator via the
- * <code>init_global.js</code> script, loaded as classpath resource.</p>
- * <p>
- * The SCXML system variables are bound as <code>"_scxmlSystemContext"</code> variable in the ENGINE_SCOPE
- * as needed for the <code>init_global.js</code> script in the global context.
- * This variable is bound to the ENGINE_SCOPE to ensure it cannot be 'shadowed' by an overriding variable assignment.
- * </p>
- * <p>
- * The provided SCXML Context variables are bound via the GLOBAL_SCOPE using a {@link JSBindings} wrapper for each
- * invocation.
- * </p>
- * <p>
- * As the GLOBAL_SCOPE SCXML context variables <em>can</em> be overridden, which will result in new 'shadow'
- * variables in the ENGINE_SCOPE, as well as new variables can be added to the ENGINE_SCOPE during script evaluation,
- * after script execution all ENGINE_SCOPE variables (except the <code>"_scxmlSystemContext"</code> variable) must be
- * copied/merged into the SCXML context to synchronize the SCXML context.
- * </p>
- * @param jsContext The current SCXML context
- * @return The SCXML instance shared ScriptContext
- * @throws ScriptException Thrown if the initialization of the Global Javascript engine itself failed
- */
- protected ScriptContext getScriptContext(final JSContext jsContext) throws ScriptException {
- if (scriptContext == null) {
- scriptContext = new SimpleScriptContext();
- scriptContext.setBindings(getEngine().createBindings(), ScriptContext.ENGINE_SCOPE);
- scriptContext.setBindings(new JSBindings(jsContext), ScriptContext.GLOBAL_SCOPE);
- scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars());
- getEngine().eval(initGlobalsScript, scriptContext);
- }
- else {
- // ensure updated / replaced SystemContext is used (like after SCXML instance go/reset)
- scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars());
- ((JSBindings)scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE)).setContext(jsContext);
- }
- return scriptContext;
- }
-
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
- /**
- * Javascript engine semantics, using a retained global state, requires global SCXML context execution
- * @return true
- */
- @Override
- public boolean requiresGlobalContext() {
- return true;
- }
-
- /**
- * Creates a child context.
- *
- * @return Returns a new child JSContext.
- *
- */
- @Override
- public Context newContext(final Context parent) {
- return new JSContext(parent);
- }
-
/**
* Evaluates a Javascript expression using an SCXML instance shared {@link #getScriptContext(JSContext)}.
* <p>
@@ -275,18 +186,87 @@ public class JSEvaluator extends AbstractBaseEvaluator {
}
/**
- * Copy the Javscript global context (i.e. nashorn Global instance) variables to SCXML {@code jsContext}
- * in order to make sure all the new global variables set by the JavaScript engine after evaluation are
- * available from {@link JSContext} instance as well.
- * <p>Note: the internal <code>"_scxmlSystemContext</code> variable is always skipped.</p>
- * @param global The Javascript Bindings holding the Javascript Global context variables
- * @param jsContext The SCXML context to copy/merge the variables into
+ * Gets the singleton ScriptEngine, initializing it on first access
+ * @return The ScriptEngine
*/
- private void copyJavascriptGlobalsToScxmlContext(final Bindings global, final JSContext jsContext) {
- if (global != null) {
- for (final String key : global.keySet()) {
- if (!SCXML_SYSTEM_CONTEXT.equals(key)) {
- jsContext.set(key, global.get(key));
+ protected ScriptEngine getEngine() {
+ if (engine == null) {
+ initEngine();
+ }
+ return engine;
+ }
+
+ /**
+ * Gets the current ScriptContext or create a new one.
+ * <p>
+ * The ScriptContext is (to be) shared across invocations for the same SCXML instance as it holds the Javascript 'global'
+ * context.
+ * </p>
+ * <p>
+ * The ScriptContext is using a {@link ScriptContext#ENGINE_SCOPE} as provided by the engine, which in case of Nashorn
+ * is bound to the Javscript global context. Note: do <em>not</em> confuse this with the {@link ScriptContext#GLOBAL_SCOPE} binding.
+ * </p>
+ * <p>For a newly created ScriptContext (and thus a new Javascript global context), the Javascript global context is
+ * initialized with the required and protected SCXML system variables and builtin In() operator via the
+ * <code>init_global.js</code> script, loaded as classpath resource.</p>
+ * <p>
+ * The SCXML system variables are bound as <code>"_scxmlSystemContext"</code> variable in the ENGINE_SCOPE
+ * as needed for the <code>init_global.js</code> script in the global context.
+ * This variable is bound to the ENGINE_SCOPE to ensure it cannot be 'shadowed' by an overriding variable assignment.
+ * </p>
+ * <p>
+ * The provided SCXML Context variables are bound via the GLOBAL_SCOPE using a {@link JSBindings} wrapper for each
+ * invocation.
+ * </p>
+ * <p>
+ * As the GLOBAL_SCOPE SCXML context variables <em>can</em> be overridden, which will result in new 'shadow'
+ * variables in the ENGINE_SCOPE, as well as new variables can be added to the ENGINE_SCOPE during script evaluation,
+ * after script execution all ENGINE_SCOPE variables (except the <code>"_scxmlSystemContext"</code> variable) must be
+ * copied/merged into the SCXML context to synchronize the SCXML context.
+ * </p>
+ * @param jsContext The current SCXML context
+ * @return The SCXML instance shared ScriptContext
+ * @throws ScriptException Thrown if the initialization of the Global Javascript engine itself failed
+ */
+ protected ScriptContext getScriptContext(final JSContext jsContext) throws ScriptException {
+ if (scriptContext == null) {
+ scriptContext = new SimpleScriptContext();
+ scriptContext.setBindings(getEngine().createBindings(), ScriptContext.ENGINE_SCOPE);
+ scriptContext.setBindings(new JSBindings(jsContext), ScriptContext.GLOBAL_SCOPE);
+ scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars());
+ getEngine().eval(initGlobalsScript, scriptContext);
+ }
+ else {
+ // ensure updated / replaced SystemContext is used (like after SCXML instance go/reset)
+ scriptContext.getBindings(ScriptContext.ENGINE_SCOPE).put(SCXML_SYSTEM_CONTEXT, jsContext.getSystemContext().getVars());
+ ((JSBindings)scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE)).setContext(jsContext);
+ }
+ return scriptContext;
+ }
+
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
+
+ /**
+ * Initialize the singleton Javascript ScriptEngine to be used with a separate ScriptContext for each SCXML instance
+ * not sharing their global scope, see {@link #getScriptContext(JSContext)}.
+ * <p>
+ * The SCXML required protected system variables and (possible) other Javascript global initializations are defined
+ * in a <code>init_global.js</code> script which is pre-loaded as (classpath) resource, to be executed once during
+ * initialization of a new Javascript (Nashorn) Global.
+ * </p>
+ */
+ protected synchronized void initEngine() {
+ if (engine == null) {
+ engine = new ScriptEngineManager().getEngineByName("JavaScript");
+ if (initGlobalsScript == null) {
+ try {
+ initGlobalsScript = IOUtils.toString(JSEvaluator.class.getResourceAsStream("init_global.js"), "UTF-8");
+ }
+ catch (final IOException ioe) {
+ throw new UncheckedIOException("Failed to load init_global.js from classpath", ioe);
}
}
}
@@ -307,4 +287,24 @@ public class JSEvaluator extends AbstractBaseEvaluator {
ctx.setLocal(id, eval(ctx, "Java.from("+id+")"));
}
}
+
+ /**
+ * Creates a child context.
+ *
+ * @return Returns a new child JSContext.
+ *
+ */
+ @Override
+ public Context newContext(final Context parent) {
+ return new JSContext(parent);
+ }
+
+ /**
+ * Javascript engine semantics, using a retained global state, requires global SCXML context execution
+ * @return true
+ */
+ @Override
+ public boolean requiresGlobalContext() {
+ return true;
+ }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java
index bbb4db43..34989aaa 100644
--- a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java
+++ b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlContext.java
@@ -36,15 +36,6 @@ public class JexlContext extends SimpleContext
public JexlContext() {
}
- /**
- * Constructor with initial vars.
- * @param parent The parent context
- * @param initialVars The initial set of variables.
- */
- public JexlContext(final Context parent, final Map<String, Object> initialVars) {
- super(parent, initialVars);
- }
-
/**
* Constructor with parent context.
*
@@ -53,5 +44,14 @@ public class JexlContext extends SimpleContext
public JexlContext(final Context parent) {
super(parent);
}
+
+ /**
+ * Constructor with initial vars.
+ * @param parent The parent context
+ * @param initialVars The initial set of variables.
+ */
+ public JexlContext(final Context parent, final Map<String, Object> initialVars) {
+ super(parent, initialVars);
+ }
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
index 643e3736..a490ba16 100644
--- a/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/jexl/JexlEvaluator.java
@@ -42,18 +42,8 @@ import org.apache.commons.scxml2.model.SCXML;
*/
public class JexlEvaluator extends AbstractBaseEvaluator {
- /** Serial version UID. */
- private static final long serialVersionUID = 1L;
-
- public static final String SUPPORTED_DATA_MODEL = "jexl";
-
public static class JexlEvaluatorProvider implements EvaluatorProvider {
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
@Override
public Evaluator getEvaluator() {
return new JexlEvaluator();
@@ -63,8 +53,18 @@ public class JexlEvaluator extends AbstractBaseEvaluator {
public Evaluator getEvaluator(final SCXML document) {
return new JexlEvaluator();
}
+
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
}
+ /** Serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ public static final String SUPPORTED_DATA_MODEL = "jexl";
+
/** Error message if evaluation context is not a JexlContext. */
private static final String ERR_CTX_TYPE = "Error evaluating JEXL "
+ "expression, Context must be a org.apache.commons.scxml2.env.jexl.JexlContext";
@@ -79,14 +79,19 @@ public class JexlEvaluator extends AbstractBaseEvaluator {
jexlEngine = getJexlEngine();
}
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
- @Override
- public boolean requiresGlobalContext() {
- return false;
+ /**
+ * Create the internal JexlEngine member during the initialization.
+ * This method can be overriden to specify more detailed options
+ * into the JexlEngine.
+ * @return new JexlEngine instance
+ */
+ protected JexlEngine createJexlEngine() {
+ // With null prefix, define top-level user-defined functions.
+ // See javadoc of org.apache.commons.jexl2.JexlEngine#setFunctions(Map<String,Object> funcs) for detail.
+ final Map<String, Object> funcs = new HashMap<>();
+ funcs.put(null, JexlBuiltin.class);
+ JexlPermissions permissions = JexlPermissions.RESTRICTED.compose("org.apache.commons.scxml2.*");
+ return new JexlBuilder().permissions(permissions).namespaces(funcs).cache(256).create();
}
/**
@@ -163,30 +168,16 @@ public class JexlEvaluator extends AbstractBaseEvaluator {
}
/**
- * Create a new child context.
+ * Create a new context which is the summation of contexts from the
+ * current state to document root, child has priority over parent
+ * in scoping rules.
*
- * @param parent parent context
- * @return new child context
- * @see Evaluator#newContext(Context)
- */
- @Override
- public Context newContext(final Context parent) {
- return new JexlContext(parent);
- }
-
- /**
- * Create the internal JexlEngine member during the initialization.
- * This method can be overriden to specify more detailed options
- * into the JexlEngine.
- * @return new JexlEngine instance
+ * @param nodeCtx The JexlContext for this state.
+ * @return The effective JexlContext for the path leading up to
+ * document root.
*/
- protected JexlEngine createJexlEngine() {
- // With null prefix, define top-level user-defined functions.
- // See javadoc of org.apache.commons.jexl2.JexlEngine#setFunctions(Map<String,Object> funcs) for detail.
- final Map<String, Object> funcs = new HashMap<>();
- funcs.put(null, JexlBuiltin.class);
- JexlPermissions permissions = JexlPermissions.RESTRICTED.compose("org.apache.commons.scxml2.*");
- return new JexlBuilder().permissions(permissions).namespaces(funcs).cache(256).create();
+ protected JexlContext getEffectiveContext(final JexlContext nodeCtx) {
+ return new JexlContext(nodeCtx, new EffectiveContextMap(nodeCtx));
}
/**
@@ -210,17 +201,26 @@ public class JexlEvaluator extends AbstractBaseEvaluator {
return engine;
}
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
+
/**
- * Create a new context which is the summation of contexts from the
- * current state to document root, child has priority over parent
- * in scoping rules.
+ * Create a new child context.
*
- * @param nodeCtx The JexlContext for this state.
- * @return The effective JexlContext for the path leading up to
- * document root.
+ * @param parent parent context
+ * @return new child context
+ * @see Evaluator#newContext(Context)
*/
- protected JexlContext getEffectiveContext(final JexlContext nodeCtx) {
- return new JexlContext(nodeCtx, new EffectiveContextMap(nodeCtx));
+ @Override
+ public Context newContext(final Context parent) {
+ return new JexlContext(parent);
+ }
+
+ @Override
+ public boolean requiresGlobalContext() {
+ return false;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalContext.java b/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalContext.java
index 23af0de4..84e017f7 100644
--- a/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalContext.java
+++ b/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalContext.java
@@ -54,12 +54,13 @@ public class MinimalContext extends SimpleContext {
}
@Override
- public void set(final String name, final Object value) {
+ public Object get(final String name) {
+ return null;
}
@Override
- public Object get(final String name) {
- return null;
+ public Map<String, Object> getVars() {
+ return Collections.emptyMap();
}
@Override
@@ -77,15 +78,14 @@ public class MinimalContext extends SimpleContext {
}
@Override
- public void setLocal(final String name, final Object value) {
+ public void set(final String name, final Object value) {
}
@Override
- protected void setVars(final Map<String, Object> vars) {
+ public void setLocal(final String name, final Object value) {
}
@Override
- public Map<String, Object> getVars() {
- return Collections.emptyMap();
+ protected void setVars(final Map<String, Object> vars) {
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalEvaluator.java b/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalEvaluator.java
index 93adb094..c1846649 100644
--- a/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalEvaluator.java
+++ b/src/main/java/org/apache/commons/scxml2/env/minimal/MinimalEvaluator.java
@@ -33,18 +33,8 @@ import org.apache.commons.scxml2.model.SCXML;
*/
public class MinimalEvaluator implements Evaluator, Serializable {
- /** Serial version UID. */
- private static final long serialVersionUID = 1L;
-
- public static final String SUPPORTED_DATA_MODEL = Evaluator.NULL_DATA_MODEL;
-
public static class MinimalEvaluatorProvider implements EvaluatorProvider {
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
- }
-
@Override
public Evaluator getEvaluator() {
return new MinimalEvaluator();
@@ -54,17 +44,17 @@ public class MinimalEvaluator implements Evaluator, Serializable {
public Evaluator getEvaluator(final SCXML document) {
return new MinimalEvaluator();
}
- }
- @Override
- public String getSupportedDatamodel() {
- return SUPPORTED_DATA_MODEL;
+ @Override
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
+ }
}
- @Override
- public boolean requiresGlobalContext() {
- return true;
- }
+ /** Serial version UID. */
+ private static final long serialVersionUID = 1L;
+
+ public static final String SUPPORTED_DATA_MODEL = Evaluator.NULL_DATA_MODEL;
@Override
public Object cloneData(final Object data) {
@@ -76,6 +66,11 @@ public class MinimalEvaluator implements Evaluator, Serializable {
return expr;
}
+ @Override
+ public void evalAssign(final Context ctx, final String location, final Object data) throws SCXMLExpressionException {
+ throw new UnsupportedOperationException("Assign expressions are not supported by the \"null\" datamodel");
+ }
+
@Override
public Boolean evalCond(final Context ctx, final String expr) throws SCXMLExpressionException {
// only support the "In(stateId)" predicate
@@ -88,17 +83,22 @@ public class MinimalEvaluator implements Evaluator, Serializable {
}
@Override
- public void evalAssign(final Context ctx, final String location, final Object data) throws SCXMLExpressionException {
- throw new UnsupportedOperationException("Assign expressions are not supported by the \"null\" datamodel");
+ public Object evalScript(final Context ctx, final String script) throws SCXMLExpressionException {
+ throw new UnsupportedOperationException("Scripts are not supported by the \"null\" datamodel");
}
@Override
- public Object evalScript(final Context ctx, final String script) throws SCXMLExpressionException {
- throw new UnsupportedOperationException("Scripts are not supported by the \"null\" datamodel");
+ public String getSupportedDatamodel() {
+ return SUPPORTED_DATA_MODEL;
}
@Override
public Context newContext(final Context parent) {
return parent instanceof MinimalContext ? parent : new MinimalContext(parent);
}
+
+ @Override
+ public boolean requiresGlobalContext() {
+ return true;
+ }
}
diff --git a/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java b/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java
index c8dd791b..43462f44 100644
--- a/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java
+++ b/src/main/java/org/apache/commons/scxml2/invoke/Invoker.java
@@ -67,25 +67,13 @@ import org.apache.commons.scxml2.TriggerEvent;
public interface Invoker {
/**
- * @return get the invoke ID provided by the parent state machine executor
- */
- String getInvokeId();
-
- /**
- * Sets the invoke ID provided by the parent state machine executor
- * Implementations must use this ID for constructing the event name for
- * the special "done" event (and optionally, for other event names
- * as well).
+ * Cancel this invocation.
*
- * @param invokeId The invoke ID provided by the parent state machine executor.
- */
- void setInvokeId(String invokeId);
-
- /**
- * Sets the parent SCXMLExecutor through which this Invoker is initiated
- * @param scxmlExecutor the parent SCXMLExecutor
+ * @throws InvokerException In case there is a fatal problem with
+ * canceling this invoke.
*/
- void setParentSCXMLExecutor(SCXMLExecutor scxmlExecutor);
+ void cancel()
+ throws InvokerException;
/**
* Gets the child IO Processor to register for communication with
@@ -95,6 +83,11 @@ public interface Invoker {
*/
SCXMLIOProcessor getChildIOProcessor();
+ /**
+ * @return get the invoke ID provided by the parent state machine executor
+ */
+ String getInvokeId();
+
/**
* Invoke the SCXML document located at an external URL.
*
@@ -133,12 +126,19 @@ public interface Invoker {
throws InvokerException;
/**
- * Cancel this invocation.
+ * Sets the invoke ID provided by the parent state machine executor
+ * Implementations must use this ID for constructing the event name for
+ * the special "done" event (and optionally, for other event names
+ * as well).
*
- * @throws InvokerException In case there is a fatal problem with
- * canceling this invoke.
+ * @param invokeId The invoke ID provided by the parent state machine executor.
*/
- void cancel()
- throws InvokerException;
+ void setInvokeId(String invokeId);
+
+ /**
+ * Sets the parent SCXMLExecutor through which this Invoker is initiated
+ * @param scxmlExecutor the parent SCXMLExecutor
+ */
+ void setParentSCXMLExecutor(SCXMLExecutor scxmlExecutor);
}
diff --git a/src/main/java/org/apache/commons/scxml2/invoke/InvokerException.java b/src/main/java/org/apache/commons/scxml2/invoke/InvokerException.java
index 066dad41..194a17b3 100644
--- a/src/main/java/org/apache/commons/scxml2/invoke/InvokerException.java
+++ b/src/main/java/org/apache/commons/scxml2/invoke/InvokerException.java
@@ -35,14 +35,6 @@ public class InvokerException extends Exception {
super(message);
}
- /**
- * @see Exception#Exception(Throwable)
- * @param cause The cause
- */
- public InvokerException(final Throwable cause) {
- super(cause);
- }
-
/**
* @see Exception#Exception(String, Throwable)
* @param message The error message
@@ -53,5 +45,13 @@ public class InvokerException extends Exception {
super(message, cause);
}
+ /**
+ * @see Exception#Exception(Throwable)
+ * @param cause The cause
+ */
+ public InvokerException(final Throwable cause) {
+ super(cause);
+ }
+
}
diff --git a/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java b/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java
index 0d12f820..0193c496 100644
--- a/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java
+++ b/src/main/java/org/apache/commons/scxml2/invoke/SimpleSCXMLInvoker.java
@@ -55,34 +55,43 @@ public class SimpleSCXMLInvoker implements Invoker, Serializable {
* {@inheritDoc}.
*/
@Override
- public String getInvokeId() {
- return invokeId;
+ public void cancel()
+ throws InvokerException {
+ cancelled = true;
+ executor.getParentSCXMLIOProcessor().close();
+ executor.addEvent(new EventBuilder("cancel.invoke."+ invokeId, TriggerEvent.CANCEL_EVENT).build());
}
- /**
- * {@inheritDoc}.
- */
- @Override
- public void setInvokeId(final String invokeId) {
- this.invokeId = invokeId;
- this.cancelled = false;
+ protected void execute(final SCXML scxml, final Map<String, Object> params) throws InvokerException {
+ try {
+ executor = new SCXMLExecutor(parentSCXMLExecutor, invokeId, scxml);
+ }
+ catch (final ModelException me) {
+ throw new InvokerException(me);
+ }
+ executor.addListener(scxml, new SimpleSCXMLListener());
+ try {
+ executor.run(params);
+ } catch (final ModelException me) {
+ throw new InvokerException(me.getMessage(), me.getCause());
+ }
}
/**
* {@inheritDoc}.
*/
@Override
- public void setParentSCXMLExecutor(final SCXMLExecutor parentSCXMLExecutor) {
- this.parentSCXMLExecutor = parentSCXMLExecutor;
+ public SCXMLIOProcessor getChildIOProcessor() {
+ // not used
+ return executor;
}
/**
* {@inheritDoc}.
*/
@Override
- public SCXMLIOProcessor getChildIOProcessor() {
- // not used
- return executor;
+ public String getInvokeId() {
+ return invokeId;
}
/**
@@ -115,21 +124,6 @@ public class SimpleSCXMLInvoker implements Invoker, Serializable {
execute(scxml, params);
}
- protected void execute(final SCXML scxml, final Map<String, Object> params) throws InvokerException {
- try {
- executor = new SCXMLExecutor(parentSCXMLExecutor, invokeId, scxml);
- }
- catch (final ModelException me) {
- throw new InvokerException(me);
- }
- executor.addListener(scxml, new SimpleSCXMLListener());
- try {
- executor.run(params);
- } catch (final ModelException me) {
- throw new InvokerException(me.getMessage(), me.getCause());
- }
- }
-
/**
* {@inheritDoc}.
*/
@@ -145,11 +139,17 @@ public class SimpleSCXMLInvoker implements Invoker, Serializable {
* {@inheritDoc}.
*/
@Override
- public void cancel()
- throws InvokerException {
- cancelled = true;
- executor.getParentSCXMLIOProcessor().close();
- executor.addEvent(new EventBuilder("cancel.invoke."+ invokeId, TriggerEvent.CANCEL_EVENT).build());
+ public void setInvokeId(final String invokeId) {
+ this.invokeId = invokeId;
+ this.cancelled = false;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void setParentSCXMLExecutor(final SCXMLExecutor parentSCXMLExecutor) {
+ this.parentSCXMLExecutor = parentSCXMLExecutor;
}
}
diff --git a/src/main/java/org/apache/commons/scxml2/io/ContentParser.java b/src/main/java/org/apache/commons/scxml2/io/ContentParser.java
index 3f9f45fe..87863b75 100644
--- a/src/main/java/org/apache/commons/scxml2/io/ContentParser.java
+++ b/src/main/java/org/apache/commons/scxml2/io/ContentParser.java
@@ -51,48 +51,31 @@ public class ContentParser {
public static final ContentParser DEFAULT_PARSER = new ContentParser();
/**
- * Jackson JSON ObjectMapper
- */
- private final ObjectMapper jsonObjectMapper;
-
- /**
- * Default constructor initializing a Jackson ObjectMapper allowing embedded comments, including YAML style
+ * Check if content starts with JSON object '{' or array '[' marker
+ * @param content text to check
+ * @return true if content start with '{' or '[' character
*/
- public ContentParser() {
- this.jsonObjectMapper = new ObjectMapper();
- jsonObjectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
- jsonObjectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
+ public static boolean hasJsonSignature(final String content) {
+ final char c = !content.isEmpty() ? content.charAt(0) : 0;
+ return c == '{' || c == '[';
}
/**
- * Constructor with a custom configured Jackson ObjectMapper
- * @param jsonObjectMapper custom configured Jackson ObjectMapper
+ * Check if content indicates its an XML document
+ * @param content content to check
+ * @return true if content indicates its an XML document
*/
- public ContentParser(final ObjectMapper jsonObjectMapper) {
- this.jsonObjectMapper = jsonObjectMapper;
+ public static boolean hasXmlSignature(final String content) {
+ return content != null && content.startsWith("<?xml ");
}
/**
- * Trim pre/post-fixed whitespace from content string
- * @param content content to trim
- * @return trimmed content
+ * Check if a character is whitespace (space, tab, newline, cr) or not
+ * @param c character to check
+ * @return true if character is whitespace
*/
- public static String trimContent(final String content) {
- if (content != null) {
- int start = 0;
- int length = content.length();
- while (start < length && isWhiteSpace(content.charAt(start))) {
- start++;
- }
- while (length > start && isWhiteSpace(content.charAt(length - 1))) {
- length--;
- }
- if (start == length) {
- return "";
- }
- return content.substring(start, length);
- }
- return null;
+ public static boolean isWhiteSpace(final char c) {
+ return c==' ' || c=='\n' || c=='\t' || c=='\r';
}
/**
@@ -129,31 +112,73 @@ public class ContentParser {
}
/**
- * Check if a character is whitespace (space, tab, newline, cr) or not
- * @param c character to check
- * @return true if character is whitespace
+ * Trim pre/post-fixed whitespace from content string
+ * @param content content to trim
+ * @return trimmed content
*/
- public static boolean isWhiteSpace(final char c) {
- return c==' ' || c=='\n' || c=='\t' || c=='\r';
+ public static String trimContent(final String content) {
+ if (content != null) {
+ int start = 0;
+ int length = content.length();
+ while (start < length && isWhiteSpace(content.charAt(start))) {
+ start++;
+ }
+ while (length > start && isWhiteSpace(content.charAt(length - 1))) {
+ length--;
+ }
+ if (start == length) {
+ return "";
+ }
+ return content.substring(start, length);
+ }
+ return null;
}
/**
- * Check if content starts with JSON object '{' or array '[' marker
- * @param content text to check
- * @return true if content start with '{' or '[' character
+ * Jackson JSON ObjectMapper
*/
- public static boolean hasJsonSignature(final String content) {
- final char c = !content.isEmpty() ? content.charAt(0) : 0;
- return c == '{' || c == '[';
+ private final ObjectMapper jsonObjectMapper;
+
+ /**
+ * Default constructor initializing a Jackson ObjectMapper allowing embedded comments, including YAML style
+ */
+ public ContentParser() {
+ this.jsonObjectMapper = new ObjectMapper();
+ jsonObjectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+ jsonObjectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
}
/**
- * Check if content indicates its an XML document
- * @param content content to check
- * @return true if content indicates its an XML document
+ * Constructor with a custom configured Jackson ObjectMapper
+ * @param jsonObjectMapper custom configured Jackson ObjectMapper
*/
- public static boolean hasXmlSignature(final String content) {
- return content != null && content.startsWith("<?xml ");
+ public ContentParser(final ObjectMapper jsonObjectMapper) {
+ this.jsonObjectMapper = jsonObjectMapper;
+ }
+
+ /**
+ * Parse a string into a ParsedValue content object, following the SCXML rules as specified for the ECMAscript (section B.2.1) Data Model
+ * <ul>
+ * <li>if the content can be interpreted as JSON, it will be parsed as JSON into an 'raw' object model</li>
+ * <li>if the content can be interpreted as XML, it will be parsed into a XML DOM element</li>
+ * <li>otherwise the content will be treated (cleaned) as a space-normalized string literal</li>
+ * </ul>
+ * @param content the content to parse
+ * @return the parsed content object
+ * @throws IOException In case of parsing exceptions
+ */
+ public ParsedValue parseContent(final String content) throws IOException {
+ if (content != null) {
+ final String src = trimContent(content);
+ if (hasJsonSignature(src)) {
+ return new JsonValue(parseJson(src), false);
+ }
+ if (hasXmlSignature(src)) {
+ return new NodeValue(parseXml(src));
+ }
+ return new TextValue(spaceNormalizeContent(src), false);
+ }
+ return null;
}
/**
@@ -167,13 +192,16 @@ public class ContentParser {
}
/**
- * Transforms a jsonObject to a json String
- * @param jsonObject object to transform
- * @return json string
- * @throws IOException if IO error occurs while serializing it to JSON
+ * Load a resource (URL) as an UTF-8 encoded content string to be parsed into a ParsedValue content object through {@link #parseContent(String)}
+ * @param resourceURL Resource URL to load content from
+ * @return the parsed content object
+ * @throws IOException In case of loading or parsing exceptions
*/
- public String toJson(final Object jsonObject) throws IOException {
- return jsonObjectMapper.writeValueAsString(jsonObject);
+ public ParsedValue parseResource(final String resourceURL) throws IOException {
+ try (InputStream in = new URL(resourceURL).openStream()) {
+ final String content = IOUtils.toString(in, "UTF-8");
+ return parseContent(content);
+ }
}
/**
@@ -192,6 +220,16 @@ public class ContentParser {
return doc != null ? doc.getDocumentElement() : null;
}
+ /**
+ * Transforms a jsonObject to a json String
+ * @param jsonObject object to transform
+ * @return json string
+ * @throws IOException if IO error occurs while serializing it to JSON
+ */
+ public String toJson(final Object jsonObject) throws IOException {
+ return jsonObjectMapper.writeValueAsString(jsonObject);
+ }
+
/**
* Transforms a XML Node to XML
* @param node node to transform
@@ -213,42 +251,4 @@ public class ContentParser {
throw new IOException(e);
}
}
-
- /**
- * Parse a string into a ParsedValue content object, following the SCXML rules as specified for the ECMAscript (section B.2.1) Data Model
- * <ul>
- * <li>if the content can be interpreted as JSON, it will be parsed as JSON into an 'raw' object model</li>
- * <li>if the content can be interpreted as XML, it will be parsed into a XML DOM element</li>
- * <li>otherwise the content will be treated (cleaned) as a space-normalized string literal</li>
- * </ul>
- * @param content the content to parse
- * @return the parsed content object
- * @throws IOException In case of parsing exceptions
- */
- public ParsedValue parseContent(final String content) throws IOException {
- if (content != null) {
- final String src = trimContent(content);
- if (hasJsonSignature(src)) {
- return new JsonValue(parseJson(src), false);
- }
- if (hasXmlSignature(src)) {
- return new NodeValue(parseXml(src));
- }
- return new TextValue(spaceNormalizeContent(src), false);
- }
- return null;
- }
-
- /**
- * Load a resource (URL) as an UTF-8 encoded content string to be parsed into a ParsedValue content object through {@link #parseContent(String)}
- * @param resourceURL Resource URL to load content from
- * @return the parsed content object
- * @throws IOException In case of loading or parsing exceptions
- */
- public ParsedValue parseResource(final String resourceURL) throws IOException {
- try (InputStream in = new URL(resourceURL).openStream()) {
- final String content = IOUtils.toString(in, "UTF-8");
- return parseContent(content);
- }
- }
}
diff --git a/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java b/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java
index 05eb23ed..caead5e0 100644
--- a/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java
+++ b/src/main/java/org/apache/commons/scxml2/io/ModelUpdater.java
@@ -117,50 +117,31 @@ final class ModelUpdater {
+ " must specify either one, but not both.";
/**
- * Discourage instantiation since this is a utility class.
- */
- private ModelUpdater() {
- }
-
- /*
- * Post-processing methods to make the SCXML object SCXMLExecutor ready.
- */
- /**
- * <p>Update the SCXML object model and make it SCXMLExecutor ready.
- * This is part of post-read processing, and sets up the necessary
- * object references throughtout the SCXML object model for the parsed
- * document.</p>
+ * Gets a transition target identifier for error messages. This method is
+ * only called to produce an appropriate log message in some error
+ * conditions.
*
- * @param scxml The SCXML object (output from SCXMLReader)
- * @throws ModelException If the object model is flawed
+ * @param tt The <code>TransitionTarget</code> object
+ * @return The transition target identifier for the error message
*/
- static void updateSCXML(final SCXML scxml) throws ModelException {
- initDocumentOrder(scxml.getChildren(), 1);
-
- final String initial = scxml.getInitial();
- final SimpleTransition initialTransition = new SimpleTransition();
-
- if (initial != null) {
-
- initialTransition.setNext(scxml.getInitial());
- updateTransition(initialTransition, scxml.getTargets());
-
- if (initialTransition.getTargets().isEmpty()) {
- logAndThrowModelError(ERR_SCXML_NO_INIT, new Object[] {
- initial });
+ private static String getName(final TransitionTarget tt) {
+ String name = "anonymous transition target";
+ if (tt instanceof State) {
+ name = "anonymous state";
+ if (tt.getId() != null) {
+ name = "state with ID \"" + tt.getId() + "\"";
+ }
+ } else if (tt instanceof Parallel) {
+ name = "anonymous parallel";
+ if (tt.getId() != null) {
+ name = "parallel with ID \"" + tt.getId() + "\"";
}
} else {
- // If 'initial' is not specified, the default initial state is
- // the first child state in document order.
- initialTransition.getTargets().add(scxml.getFirstChild());
+ if (tt.getId() != null) {
+ name = "transition target with ID \"" + tt.getId() + "\"";
+ }
}
-
- scxml.setInitialTransition(initialTransition);
- final Map<String, TransitionTarget> targets = scxml.getTargets();
- updateEnterableStates(scxml.getChildren(), targets);
-
- scxml.getInitialTransition().setObservableId(1);
- initObservables(scxml.getChildren(), 2);
+ return name;
}
/**
@@ -218,83 +199,20 @@ final class ModelUpdater {
}
/**
- * Update this State object (part of post-read processing).
- * Also checks for any errors in the document.
- *
- * @param state The State object
- * @param targets The global Map of all transition targets
- * @throws ModelException If the object model is flawed
- */
- private static void updateState(final State state, final Map<String, TransitionTarget> targets)
- throws ModelException {
- final List<EnterableState> children = state.getChildren();
- if (state.isComposite()) {
- //initialize next / initial
- Initial ini = state.getInitial();
- if (ini == null) {
- state.setFirst(children.get(0).getId());
- ini = state.getInitial();
- }
- final SimpleTransition initialTransition = ini.getTransition();
- updateTransition(initialTransition, targets);
- final Set<TransitionTarget> initialStates = initialTransition.getTargets();
- // we have to allow for an indirect descendant initial (targets)
- //check that initialState is a descendant of s
- if (initialStates.isEmpty()) {
- logAndThrowModelError(ERR_STATE_BAD_INIT,
- new Object[] {getName(state)});
- } else {
- for (final TransitionTarget initialState : initialStates) {
- if (!initialState.isDescendantOf(state)) {
- logAndThrowModelError(ERR_STATE_BAD_INIT,
- new Object[] {getName(state)});
- }
- }
- }
- }
- else if (state.getInitial() != null) {
- logAndThrowModelError(ERR_UNSUPPORTED_INIT, new Object[] {getName(state)});
- }
-
- final List<History> histories = state.getHistory();
- if (!histories.isEmpty() && state.isSimple()) {
- logAndThrowModelError(ERR_HISTORY_SIMPLE_STATE,
- new Object[] {getName(state)});
- }
- for (final History history : histories) {
- updateHistory(history, targets, state);
- }
- for (final Transition transition : state.getTransitionsList()) {
- updateTransition(transition, targets);
- }
-
- for (final Invoke inv : state.getInvokes()) {
- if (inv.getSrc() != null && inv.getSrcexpr() != null) {
- logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC, new Object[] {getName(state)});
- }
- }
-
- updateEnterableStates(children, targets);
- }
-
- /**
- * Update this Parallel object (part of post-read processing).
+ * Log an error discovered in post-read processing.
*
- * @param parallel The Parallel object
- * @param targets The global Map of all transition targets
- * @throws ModelException If the object model is flawed
+ * @param errType The type of error
+ * @param msgArgs The arguments for formatting the error message
+ * @throws ModelException The model error, always thrown.
*/
- private static void updateParallel(final Parallel parallel, final Map<String, TransitionTarget> targets)
- throws ModelException {
- updateEnterableStates(parallel.getChildren(), targets);
- for (final Transition transition : parallel.getTransitionsList()) {
- updateTransition(transition, targets);
- }
- final List<History> histories = parallel.getHistory();
- for (final History history : histories) {
- updateHistory(history, targets, parallel);
- }
- // TODO: parallel must may have invokes too
+ private static void logAndThrowModelError(final String errType,
+ final Object[] msgArgs) throws ModelException {
+ final MessageFormat msgFormat = new MessageFormat(errType);
+ final String errMsg = msgFormat.format(msgArgs);
+ final org.apache.commons.logging.Log log = LogFactory.
+ getLog(ModelUpdater.class);
+ log.error(errMsg);
+ throw new ModelException(errMsg);
}
/**
@@ -358,6 +276,127 @@ final class ModelUpdater {
}
}
+ /**
+ * Update this Parallel object (part of post-read processing).
+ *
+ * @param parallel The Parallel object
+ * @param targets The global Map of all transition targets
+ * @throws ModelException If the object model is flawed
+ */
+ private static void updateParallel(final Parallel parallel, final Map<String, TransitionTarget> targets)
+ throws ModelException {
+ updateEnterableStates(parallel.getChildren(), targets);
+ for (final Transition transition : parallel.getTransitionsList()) {
+ updateTransition(transition, targets);
+ }
+ final List<History> histories = parallel.getHistory();
+ for (final History history : histories) {
+ updateHistory(history, targets, parallel);
+ }
+ // TODO: parallel must may have invokes too
+ }
+
+ /*
+ * Post-processing methods to make the SCXML object SCXMLExecutor ready.
+ */
+ /**
+ * <p>Update the SCXML object model and make it SCXMLExecutor ready.
+ * This is part of post-read processing, and sets up the necessary
+ * object references throughtout the SCXML object model for the parsed
+ * document.</p>
+ *
+ * @param scxml The SCXML object (output from SCXMLReader)
+ * @throws ModelException If the object model is flawed
+ */
+ static void updateSCXML(final SCXML scxml) throws ModelException {
+ initDocumentOrder(scxml.getChildren(), 1);
+
+ final String initial = scxml.getInitial();
+ final SimpleTransition initialTransition = new SimpleTransition();
+
+ if (initial != null) {
+
+ initialTransition.setNext(scxml.getInitial());
+ updateTransition(initialTransition, scxml.getTargets());
+
+ if (initialTransition.getTargets().isEmpty()) {
+ logAndThrowModelError(ERR_SCXML_NO_INIT, new Object[] {
+ initial });
+ }
+ } else {
+ // If 'initial' is not specified, the default initial state is
+ // the first child state in document order.
+ initialTransition.getTargets().add(scxml.getFirstChild());
+ }
+
+ scxml.setInitialTransition(initialTransition);
+ final Map<String, TransitionTarget> targets = scxml.getTargets();
+ updateEnterableStates(scxml.getChildren(), targets);
+
+ scxml.getInitialTransition().setObservableId(1);
+ initObservables(scxml.getChildren(), 2);
+ }
+
+ /**
+ * Update this State object (part of post-read processing).
+ * Also checks for any errors in the document.
+ *
+ * @param state The State object
+ * @param targets The global Map of all transition targets
+ * @throws ModelException If the object model is flawed
+ */
+ private static void updateState(final State state, final Map<String, TransitionTarget> targets)
+ throws ModelException {
+ final List<EnterableState> children = state.getChildren();
+ if (state.isComposite()) {
+ //initialize next / initial
+ Initial ini = state.getInitial();
+ if (ini == null) {
+ state.setFirst(children.get(0).getId());
+ ini = state.getInitial();
+ }
+ final SimpleTransition initialTransition = ini.getTransition();
+ updateTransition(initialTransition, targets);
+ final Set<TransitionTarget> initialStates = initialTransition.getTargets();
+ // we have to allow for an indirect descendant initial (targets)
+ //check that initialState is a descendant of s
+ if (initialStates.isEmpty()) {
+ logAndThrowModelError(ERR_STATE_BAD_INIT,
+ new Object[] {getName(state)});
+ } else {
+ for (final TransitionTarget initialState : initialStates) {
+ if (!initialState.isDescendantOf(state)) {
+ logAndThrowModelError(ERR_STATE_BAD_INIT,
+ new Object[] {getName(state)});
+ }
+ }
+ }
+ }
+ else if (state.getInitial() != null) {
+ logAndThrowModelError(ERR_UNSUPPORTED_INIT, new Object[] {getName(state)});
+ }
+
+ final List<History> histories = state.getHistory();
+ if (!histories.isEmpty() && state.isSimple()) {
+ logAndThrowModelError(ERR_HISTORY_SIMPLE_STATE,
+ new Object[] {getName(state)});
+ }
+ for (final History history : histories) {
+ updateHistory(history, targets, state);
+ }
+ for (final Transition transition : state.getTransitionsList()) {
+ updateTransition(transition, targets);
+ }
+
+ for (final Invoke inv : state.getInvokes()) {
+ if (inv.getSrc() != null && inv.getSrcexpr() != null) {
+ logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC, new Object[] {getName(state)});
+ }
+ }
+
+ updateEnterableStates(children, targets);
+ }
+
/**
* Update this Transition object (part of post-read processing).
*
@@ -394,51 +433,6 @@ final class ModelUpdater {
}
}
- /**
- * Log an error discovered in post-read processing.
- *
- * @param errType The type of error
- * @param msgArgs The arguments for formatting the error message
- * @throws ModelException The model error, always thrown.
- */
- private static void logAndThrowModelError(final String errType,
- final Object[] msgArgs) throws ModelException {
- final MessageFormat msgFormat = new MessageFormat(errType);
- final String errMsg = msgFormat.format(msgArgs);
- final org.apache.commons.logging.Log log = LogFactory.
- getLog(ModelUpdater.class);
- log.error(errMsg);
- throw new ModelException(errMsg);
- }
-
- /**
- * Gets a transition target identifier for error messages. This method is
- * only called to produce an appropriate log message in some error
- * conditions.
- *
- * @param tt The <code>TransitionTarget</code> object
- * @return The transition target identifier for the error message
- */
- private static String getName(final TransitionTarget tt) {
- String name = "anonymous transition target";
- if (tt instanceof State) {
- name = "anonymous state";
- if (tt.getId() != null) {
- name = "state with ID \"" + tt.getId() + "\"";
- }
- } else if (tt instanceof Parallel) {
- name = "anonymous parallel";
- if (tt.getId() != null) {
- name = "parallel with ID \"" + tt.getId() + "\"";
- }
- } else {
- if (tt.getId() != null) {
- name = "transition target with ID \"" + tt.getId() + "\"";
- }
- }
- return name;
- }
-
/**
* If a transition has multiple targets, then they satisfy the following
* criteria:
@@ -482,4 +476,10 @@ final class ModelUpdater {
// least common ancestor must be a parallel
return first != null && i > 0 && first.getAncestor(i-1) instanceof Parallel;
}
+
+ /**
+ * Discourage instantiation since this is a utility class.
+ */
+ private ModelUpdater() {
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java b/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java
index b358787d..21dfe9bc 100644
--- a/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java
+++ b/src/main/java/org/apache/commons/scxml2/io/SCXMLReader.java
@@ -118,1344 +118,1139 @@ import org.xml.sax.SAXException;
*/
public final class SCXMLReader {
- private static final org.apache.commons.logging.Log logger = LogFactory.getLog(SCXMLReader.class);
-
- /**
- * By default Sun/Oracle XMLStreamReader implementation doesn't report CDATA events.
- * This can be turned on (as needed by SCXMLReader) by setting this property TRUE
- */
- public final static String XMLInputFactory_JDK_PROP_REPORT_CDATA = "http://java.sun.com/xml/stream/properties/report-cdata-event";
-
- //---------------------- PRIVATE CONSTANTS ----------------------//
- /**
- * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
- */
- private static final String SCXML_REQUIRED_VERSION = "1.0";
- /**
- * The default namespace for attributes.
- */
- private static final String XMLNS_DEFAULT = null;
-
- //---- ERROR MESSAGES ----//
+ //------------------------- CONFIGURATION CLASS -------------------------//
/**
- * Null URL passed as argument.
+ * <p>
+ * Configuration for the {@link SCXMLReader}. The configuration properties necessary for the following are
+ * covered:
+ * </p>
+ *
+ * <ul>
+ * <li>{@link XMLInputFactory} configuration properties such as {@link XMLReporter}, {@link XMLResolver} and
+ * {@link XMLEventAllocator}</li>
+ * <li>{@link XMLStreamReader} configuration properties such as <code>systemId</code> and <code>encoding</code>
+ * </li>
+ * <li>Commons SCXML object model configuration properties such as the list of custom actions and the
+ * {@link PathResolver} to use.</li>
+ * </ul>
*/
- private static final String ERR_NULL_URL = "Cannot parse null URL";
+ public static class Configuration {
- /**
- * Null path passed as argument.
- */
- private static final String ERR_NULL_PATH = "Cannot parse null path";
+ /*
+ * Configuration properties for this {@link SCXMLReader}.
+ */
+ // XMLInputFactory configuration properties.
+ /**
+ * The <code>factoryId</code> to use for the {@link XMLInputFactory}.
+ */
+ final String factoryId;
- /**
- * Null InputStream passed as argument.
- */
- private static final String ERR_NULL_ISTR = "Cannot parse null InputStream";
+ /**
+ * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
+ */
+ final ClassLoader factoryClassLoader;
- /**
- * Null Reader passed as argument.
- */
- private static final String ERR_NULL_READ = "Cannot parse null Reader";
+ /**
+ * The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
+ */
+ final XMLEventAllocator allocator;
- /**
- * Null Source passed as argument.
- */
- private static final String ERR_NULL_SRC = "Cannot parse null Source";
+ /**
+ * The map of properties (keys are property name strings, values are object property values) for the
+ * {@link XMLInputFactory}.
+ */
+ final Map<String, Object> properties;
- /**
- * Error message while attempting to define a custom action which does
- * not extend the Commons SCXML Action base class.
- */
- private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
- + " contained unknown object, class not a Commons SCXML Action class subtype: ";
+ /**
+ * The {@link XMLResolver} for the {@link XMLInputFactory}.
+ */
+ final XMLResolver resolver;
- /**
- * Parser configuration error while trying to parse stream to DOM node(s).
- */
- private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying"
- + " to parse stream into DOM node(s).";
+ /**
+ * The {@link XMLReporter} for the {@link XMLInputFactory}.
+ */
+ final XMLReporter reporter;
- /**
- * Error message when the URI in a <state>'s "src"
- * attribute does not point to a valid SCXML document, and thus cannot be
- * parsed.
- */
- private static final String ERR_STATE_SRC =
- "Source attribute in <state src=\"{0}\"> cannot be parsed";
+ // XMLStreamReader configuration properties.
+ /**
+ * The <code>encoding</code> to use for the {@link XMLStreamReader}.
+ */
+ final String encoding;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not defined in the referenced document.
- */
- private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in "
- + "<state src=\"{0}\"> is an unknown state in referenced document";
+ /**
+ * The <code>systemId</code> to use for the {@link XMLStreamReader}.
+ */
+ final String systemId;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not a <state> or <final> in
- * the referenced document.
- */
- private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment"
- + " in <state src=\"{0}\"> does not point to a <state> or <final>";
+ /**
+ * Whether to validate the input with the XML Schema for SCXML.
+ */
+ final boolean validate;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not a <state> or <final> in
- * the referenced document.
- */
- private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing"
- +" required attribute \"{1}\" value at {2}";
+ // Commons SCXML object model configuration properties.
+ /**
+ * The list of Commons SCXML custom actions that will be available for this document.
+ */
+ final List<CustomAction> customActions;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not a <state> or <final> in
- * the referenced document.
- */
- private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\""
- + "for attribute \"{1}\" in element <{2}> at {3}."
- +" Only the value \"true\" or \"false\" is allowed.";
+ /**
+ * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create.
+ */
+ final ClassLoader customActionClassLoader;
- /**
- * Error message when the element (state|parallel|final|history) uses an id value
- * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}.
- */
- private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \""
- +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}";
+ /**
+ * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes.
+ */
+ final boolean useContextClassLoaderForCustomActions;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not defined in the referenced document.
- */
- private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type "
- + "for <transition type=\"{0}\"> at {1}.";
+ // Mutable Commons SCXML object model configuration properties.
+ /**
+ * The parent SCXML document if this document is src'ed in via the <state> or <parallel> element's
+ * "src" attribute.
+ */
+ SCXML parent;
- /**
- * Error message when the target of the URI fragment in a <state>'s
- * "src" attribute is not a <state> or <final> in
- * the referenced document.
- */
- private static final String ERR_INVALID_VERSION = "The <scxml> element defines"
- +" an unsupported version \"{0}\", only version \"1.0\" is supported.";
+ /**
+ * The Commons SCXML {@link PathResolver} to use for this document.
+ */
+ PathResolver pathResolver;
- //------------------------- PUBLIC API METHODS -------------------------//
- /*
- * Public methods
- */
- /**
- * Parse the SCXML document at the supplied path.
- *
- * @param scxmlPath The real path to the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final String scxmlPath)
- throws IOException, ModelException, XMLStreamException {
+ /**
+ * Whether to silently ignore any unknown or invalid elements
+ * or to leave warning logs for those.
+ */
+ boolean silent;
- return read(scxmlPath, new Configuration());
- }
+ /**
+ * Whether to strictly throw a model exception when there are any unknown or invalid elements
+ * or to leniently allow to read the model even with those.
+ */
+ boolean strict;
- /**
- * Parse the SCXML document at the supplied path with the given {@link Configuration}.
- *
- * @param scxmlPath The real path to the SCXML document.
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final String scxmlPath, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ final ContentParser contentParser;
- if (scxmlPath == null) {
- throw new IllegalArgumentException(ERR_NULL_PATH);
+ /*
+ * Public constructors
+ */
+ /**
+ * Default constructor.
+ */
+ public Configuration() {
+ this(null, null);
}
- final SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null);
- if (scxml != null) {
- ModelUpdater.updateSCXML(scxml);
+
+ /**
+ * Package access copy constructor.
+ *
+ * @param source The source {@link Configuration} to replicate.
+ */
+ Configuration(final Configuration source) {
+ this(source.factoryId, source.factoryClassLoader, source.allocator, source.properties, source.resolver,
+ source.reporter, source.encoding, source.systemId, source.validate, source.pathResolver,
+ source.parent, source.customActions, source.customActionClassLoader,
+ source.useContextClassLoaderForCustomActions, source.silent, source.strict);
}
- return scxml;
- }
- /**
- * Parse the SCXML document at the supplied {@link URL}.
- *
- * @param scxmlURL The SCXML document {@link URL} to parse.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final URL scxmlURL)
- throws IOException, ModelException, XMLStreamException {
+ /**
+ * All purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default
+ * to <code>false</code>).
+ *
+ * @param factoryId The <code>factoryId</code> to use.
+ * @param classLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
+ * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
+ * @param properties The map of properties (keys are property name strings, values are object property values)
+ * for the {@link XMLInputFactory}.
+ * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
+ * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
+ * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
+ * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
+ * @param validate Whether to validate the input with the XML Schema for SCXML.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
+ * @param customActions The list of Commons SCXML custom actions that will be available for this document.
+ * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
+ * create.
+ * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
+ * {@link CustomAction} instances to create.
+ */
+ public Configuration(final String factoryId, final ClassLoader classLoader, final XMLEventAllocator allocator,
+ final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
+ final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
+ final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
+ final boolean useContextClassLoaderForCustomActions) {
+ this(factoryId, classLoader, allocator, properties, resolver, reporter, encoding, systemId, validate,
+ pathResolver, null, customActions, customActionClassLoader,
+ useContextClassLoaderForCustomActions);
+ }
- return read(scxmlURL, new Configuration());
- }
+ /**
+ * All-purpose package access constructor.
+ *
+ * @param factoryId The <code>factoryId</code> to use.
+ * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
+ * create.
+ * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
+ * @param properties The map of properties (keys are property name strings, values are object property values)
+ * for the {@link XMLInputFactory}.
+ * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
+ * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
+ * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
+ * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
+ * @param validate Whether to validate the input with the XML Schema for SCXML.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
+ * @param parent The parent SCXML document if this document is src'ed in via the <state> or
+ * <parallel> element's "src" attribute.
+ * @param customActions The list of Commons SCXML custom actions that will be available for this document.
+ * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
+ * create.
+ * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
+ * {@link CustomAction} instances to create.
+ */
+ Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
+ final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
+ final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
+ final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
+ final boolean useContextClassLoaderForCustomActions) {
+ this(factoryId, factoryClassLoader, allocator, properties, resolver, reporter, encoding, systemId,
+ validate, pathResolver, parent, customActions, customActionClassLoader,
+ useContextClassLoaderForCustomActions, false, false);
+ }
- /**
- * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}.
- *
- * @param scxmlURL The SCXML document {@link URL} to parse.
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final URL scxmlURL, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ /**
+ * All-purpose package access constructor.
+ *
+ * @param factoryId The <code>factoryId</code> to use.
+ * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
+ * create.
+ * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
+ * @param properties The map of properties (keys are property name strings, values are object property values)
+ * for the {@link XMLInputFactory}.
+ * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
+ * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
+ * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
+ * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
+ * @param validate Whether to validate the input with the XML Schema for SCXML.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
+ * @param parent The parent SCXML document if this document is src'ed in via the <state> or
+ * <parallel> element's "src" attribute.
+ * @param customActions The list of Commons SCXML custom actions that will be available for this document.
+ * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
+ * create.
+ * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
+ * {@link CustomAction} instances to create.
+ * @param silent Whether to silently ignore any unknown or invalid elements or to leave warning logs for those.
+ * @param strict Whether to strictly throw a model exception when there are any unknown or invalid elements
+ * or to leniently allow to read the model even with those.
+ */
+ Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
+ final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
+ final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
+ final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
+ final boolean useContextClassLoaderForCustomActions, final boolean silent, final boolean strict) {
+ this.factoryId = factoryId;
+ this.factoryClassLoader = factoryClassLoader;
+ this.allocator = allocator;
+ this.properties = (properties == null ? new HashMap<>() : properties);
+ this.resolver = resolver;
+ this.reporter = reporter;
+ this.encoding = encoding;
+ this.systemId = systemId;
+ this.validate = validate;
+ this.pathResolver = pathResolver;
+ this.parent = parent;
+ this.customActions = (customActions == null ? new ArrayList<>() : customActions);
+ this.customActionClassLoader = customActionClassLoader;
+ this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions;
+ this.silent = silent;
+ this.strict = strict;
+ this.contentParser = new ContentParser();
+ }
- if (scxmlURL == null) {
- throw new IllegalArgumentException(ERR_NULL_URL);
+ /**
+ * Minimal convenience constructor.
+ *
+ * @param reporter The {@link XMLReporter} to use for this reading.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
+ */
+ public Configuration(final XMLReporter reporter, final PathResolver pathResolver) {
+ this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, null, null, false);
}
- final SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null);
- if (scxml != null) {
- ModelUpdater.updateSCXML(scxml);
+
+ /**
+ * Convenience constructor.
+ *
+ * @param reporter The {@link XMLReporter} to use for this reading.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
+ * @param customActions The list of Commons SCXML custom actions that will be available for this document.
+ */
+ public Configuration(final XMLReporter reporter, final PathResolver pathResolver,
+ final List<CustomAction> customActions) {
+ this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, customActions, null,
+ false);
}
- return scxml;
- }
- /**
- * Parse the SCXML document supplied by the given {@link InputStream}.
- *
- * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final InputStream scxmlStream)
- throws IOException, ModelException, XMLStreamException {
+ /*
+ * Package access constructors
+ */
+ /**
+ * Convenience package access constructor.
+ *
+ * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
+ * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
+ * @param parent The parent SCXML document if this document is src'ed in via the <state> or
+ * <parallel> element's "src" attribute.
+ */
+ Configuration(final XMLReporter reporter, final PathResolver pathResolver, final SCXML parent) {
+ this(null, null, null, null, null, reporter, null, null, false, pathResolver, parent, null, null, false);
+ }
- return read(scxmlStream, new Configuration());
- }
+ /*
+ * Package access convenience methods
+ */
- /**
- * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}.
- *
- * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- public static SCXML read(final InputStream scxmlStream, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ /**
+ * Returns true if it is set to read models silently without any model error warning logs.
+ * @return true if it is set to read models silently without any model error warning logs
+ * @see #silent
+ */
+ public boolean isSilent() {
+ return silent;
+ }
- if (scxmlStream == null) {
- throw new IllegalArgumentException(ERR_NULL_ISTR);
+ /**
+ * Returns true if it is set to check model strictly with throwing exceptions on any model error.
+ * @return true if it is set to check model strictly with throwing exceptions on any model error
+ * @see #strict
+ */
+ public boolean isStrict() {
+ return strict;
}
- final SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null);
- if (scxml != null) {
- ModelUpdater.updateSCXML(scxml);
+
+ /**
+ * Turn on/off silent mode (whether to read models silently without any model error warning logs)
+ * @param silent silent mode (whether to read models silently without any model error warning logs)
+ * @see #silent
+ */
+ public void setSilent(final boolean silent) {
+ this.silent = silent;
+ }
+
+ /**
+ * Turn on/off strict model (whether to check model strictly with throwing exception on any model error)
+ * @param strict strict model (whether to check model strictly with throwing exception on any model error)
+ * @see #strict
+ */
+ public void setStrict(final boolean strict) {
+ this.strict = strict;
}
- return scxml;
}
+ private static final org.apache.commons.logging.Log logger = LogFactory.getLog(SCXMLReader.class);
+
/**
- * Parse the SCXML document supplied by the given {@link Reader}.
- *
- * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * By default Sun/Oracle XMLStreamReader implementation doesn't report CDATA events.
+ * This can be turned on (as needed by SCXMLReader) by setting this property TRUE
*/
- public static SCXML read(final Reader scxmlReader)
- throws IOException, ModelException, XMLStreamException {
+ public final static String XMLInputFactory_JDK_PROP_REPORT_CDATA = "http://java.sun.com/xml/stream/properties/report-cdata-event";
+ //---------------------- PRIVATE CONSTANTS ----------------------//
+ /**
+ * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
+ */
+ private static final String SCXML_REQUIRED_VERSION = "1.0";
- return read(scxmlReader, new Configuration());
- }
+ /**
+ * The default namespace for attributes.
+ */
+ private static final String XMLNS_DEFAULT = null;
+ //---- ERROR MESSAGES ----//
/**
- * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}.
- *
- * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * Null URL passed as argument.
*/
- public static SCXML read(final Reader scxmlReader, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ private static final String ERR_NULL_URL = "Cannot parse null URL";
- if (scxmlReader == null) {
- throw new IllegalArgumentException(ERR_NULL_READ);
- }
- final SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null);
- if (scxml != null) {
- ModelUpdater.updateSCXML(scxml);
- }
- return scxml;
- }
+ /**
+ * Null path passed as argument.
+ */
+ private static final String ERR_NULL_PATH = "Cannot parse null path";
/**
- * Parse the SCXML document supplied by the given {@link Source}.
- *
- * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * Null InputStream passed as argument.
*/
- public static SCXML read(final Source scxmlSource)
- throws IOException, ModelException, XMLStreamException {
+ private static final String ERR_NULL_ISTR = "Cannot parse null InputStream";
- return read(scxmlSource, new Configuration());
- }
+ /**
+ * Null Reader passed as argument.
+ */
+ private static final String ERR_NULL_READ = "Cannot parse null Reader";
/**
- * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}.
- *
- * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * Null Source passed as argument.
*/
- public static SCXML read(final Source scxmlSource, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ private static final String ERR_NULL_SRC = "Cannot parse null Source";
- if (scxmlSource == null) {
- throw new IllegalArgumentException(ERR_NULL_SRC);
- }
- final SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource);
- if (scxml != null) {
- ModelUpdater.updateSCXML(scxml);
- }
- return scxml;
- }
+ /**
+ * Error message while attempting to define a custom action which does
+ * not extend the Commons SCXML Action base class.
+ */
+ private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
+ + " contained unknown object, class not a Commons SCXML Action class subtype: ";
- //---------------------- PRIVATE UTILITY METHODS ----------------------//
/**
- * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not
- * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source
- * parameters must be provided.
- *
- * @param configuration The {@link Configuration} to use when parsing the SCXML document.
- * @param scxmlURL The optional SCXML document {@link URL} to parse.
- * @param scxmlPath The optional real path to the SCXML document as a string.
- * @param scxmlStream The optional {@link InputStream} providing the SCXML document.
- * @param scxmlReader The optional {@link Reader} providing the SCXML document.
- * @param scxmlSource The optional {@link Source} providing the SCXML document.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
- * (not wired up to be immediately usable).
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * Parser configuration error while trying to parse stream to DOM node(s).
*/
- private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath,
- final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource)
- throws IOException, ModelException, XMLStreamException {
+ private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying"
+ + " to parse stream into DOM node(s).";
- if (configuration.pathResolver == null) {
- if (scxmlURL != null) {
- configuration.pathResolver = new URLResolver(scxmlURL);
- } else if (scxmlPath != null) {
- configuration.pathResolver = new URLResolver(new URL(scxmlPath));
- }
- }
+ /**
+ * Error message when the URI in a <state>'s "src"
+ * attribute does not point to a valid SCXML document, and thus cannot be
+ * parsed.
+ */
+ private static final String ERR_STATE_SRC =
+ "Source attribute in <state src=\"{0}\"> cannot be parsed";
- final XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource);
+ /**
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not defined in the referenced document.
+ */
+ private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in "
+ + "<state src=\"{0}\"> is an unknown state in referenced document";
- return readDocument(reader, configuration);
- }
+ /**
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not a <state> or <final> in
+ * the referenced document.
+ */
+ private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment"
+ + " in <state src=\"{0}\"> does not point to a <state> or <final>";
- /*
- * Private utility functions for reading the SCXML document.
+ /**
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not a <state> or <final> in
+ * the referenced document.
*/
+ private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing"
+ +" required attribute \"{1}\" value at {2}";
+
/**
- * Read the SCXML document through the {@link XMLStreamReader}.
- *
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- *
- * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
- * (not wired up to be immediately usable).
- *
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not a <state> or <final> in
+ * the referenced document.
*/
- private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration)
- throws IOException, ModelException, XMLStreamException {
+ private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\""
+ + "for attribute \"{1}\" in element <{2}> at {3}."
+ +" Only the value \"true\" or \"false\" is allowed.";
- final SCXML scxml = new SCXML();
- scxml.setPathResolver(configuration.pathResolver);
- while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_SCXML.equals(name)) {
- readSCXML(reader, configuration, scxml);
- } else {
- reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
- }
- break;
- case XMLStreamConstants.NAMESPACE:
- System.err.println(reader.getNamespaceCount());
- break;
- default:
- }
- }
- return scxml;
+ /**
+ * Error message when the element (state|parallel|final|history) uses an id value
+ * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}.
+ */
+ private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \""
+ +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}";
+
+ /**
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not defined in the referenced document.
+ */
+ private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type "
+ + "for <transition type=\"{0}\"> at {1}.";
+
+ /**
+ * Error message when the target of the URI fragment in a <state>'s
+ * "src" attribute is not a <state> or <final> in
+ * the referenced document.
+ */
+ private static final String ERR_INVALID_VERSION = "The <scxml> element defines"
+ +" an unsupported version \"{0}\", only version \"1.0\" is supported.";
+
+ /**
+ * @param prefix prefix
+ * @param localName localName
+ * @return a qualified name from a prefix and localName
+ */
+ private static String createQualifiedName(final String prefix, final String localName) {
+ return (prefix != null && !prefix.isEmpty() ? prefix + ":" : "") + localName;
}
/**
- * Read the contents of this <scxml> element.
+ * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamReader} for this
+ * {@link SCXMLReader}. Exactly one of the url, path, stream, reader or source parameters must be provided.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
+ * @param configuration The {@link Configuration} to be used.
+ * @param url The {@link URL} to the SCXML document to read.
+ * @param path The optional real path to the SCXML document as a string.
+ * @param stream The optional {@link InputStream} providing the SCXML document.
+ * @param reader The optional {@link Reader} providing the SCXML document.
+ * @param source The optional {@link Source} providing the SCXML document.
*
- * @throws IOException An IO error during parsing.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @return The appropriately configured {@link XMLStreamReader}.
+ *
+ * @throws IOException Exception with the URL IO.
+ * @throws XMLStreamException A problem with the XML stream creation or an wrapped {@link SAXException}
+ * thrown in trying to validate the document against the XML Schema for SCXML.
*/
- private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml)
- throws IOException, ModelException, XMLStreamException {
+ private static XMLStreamReader getReader(final Configuration configuration, final URL url, final String path,
+ final InputStream stream, final Reader reader, final Source source)
+ throws IOException, XMLStreamException {
- scxml.setDatamodelName(readAV(reader, SCXMLConstants.ATTR_DATAMODEL));
- scxml.setExmode(readAV(reader, SCXMLConstants.ATTR_EXMODE));
- scxml.setInitial(readAV(reader, SCXMLConstants.ATTR_INITIAL));
- scxml.setName(readAV(reader, SCXMLConstants.ATTR_NAME));
- scxml.setProfile(readAV(reader, SCXMLConstants.ATTR_PROFILE));
- scxml.setVersion(readRequiredAV(reader, SCXMLConstants.ELEM_SCXML, SCXMLConstants.ATTR_VERSION));
- final String binding = readAV(reader, SCXMLConstants.ATTR_BINDING);
- if (binding != null) {
- if (SCXMLConstants.ATTR_BINDING_LATE.equals(binding)) {
- scxml.setLateBinding(true);
- } else if (SCXMLConstants.ATTR_BINDING_EARLY.equals(binding)) {
- scxml.setLateBinding(false);
- } else {
- reportIgnoredAttribute(reader, configuration, SCXMLConstants.ELEM_SCXML, SCXMLConstants.ATTR_BINDING, binding);
+ // Instantiate the XMLInputFactory
+ XMLInputFactory factory = XMLInputFactory.newInstance();
+ if (configuration.factoryId != null && configuration.factoryClassLoader != null) {
+ factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader);
+ }
+ factory.setEventAllocator(configuration.allocator);
+ if (factory.isPropertySupported(XMLInputFactory_JDK_PROP_REPORT_CDATA)) {
+ factory.setProperty(XMLInputFactory_JDK_PROP_REPORT_CDATA, Boolean.TRUE);
+ }
+ for (final Map.Entry<String, Object> property : configuration.properties.entrySet()) {
+ if (factory.isPropertySupported(property.getKey())) {
+ factory.setProperty(property.getKey(), property.getValue());
}
}
- if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) {
- throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()}));
+ factory.setXMLReporter(configuration.reporter);
+ factory.setXMLResolver(configuration.resolver);
+
+ // Consolidate InputStream options
+ InputStream urlStream = null;
+ if (url != null || path != null) {
+ final URL scxml = (url != null ? url : new URL(path));
+ final URLConnection conn = scxml.openConnection();
+ conn.setUseCaches(false);
+ urlStream = conn.getInputStream();
+ } else if (stream != null) {
+ urlStream = stream;
}
- scxml.setNamespaces(readNamespaces(reader));
- boolean hasGlobalScript = false;
+ // Create the XMLStreamReader
+ XMLStreamReader xsr = null;
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_STATE.equals(name)) {
- readState(reader, configuration, scxml, null);
- } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
- readParallel(reader, configuration, scxml, null);
- } else if (SCXMLConstants.ELEM_FINAL.equals(name)) {
- readFinal(reader, configuration, scxml, null);
- } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
- readDatamodel(reader, configuration, scxml, null);
- } else if (SCXMLConstants.ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
- readGlobalScript(reader, configuration, scxml);
- hasGlobalScript = true;
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SCXML, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SCXML, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
+ if (configuration.validate) {
+ // Validation requires us to use a Source
+
+ final URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location
+ final SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
+ Schema schema;
+ try {
+ schema = schemaFactory.newSchema(scxmlSchema);
+ } catch (final SAXException se) {
+ throw new XMLStreamException("Failed to create SCXML Schema for validation", se);
+ }
+
+ final Validator validator = schema.newValidator();
+ validator.setErrorHandler(new SimpleErrorHandler());
+
+ Source src = null;
+ if (urlStream != null) {
+ // configuration.encoding is ignored
+ if (configuration.systemId != null) {
+ src = new StreamSource(urlStream, configuration.systemId);
+ } else {
+ src = new StreamSource(urlStream);
+ }
+ } else if (reader != null) {
+ if (configuration.systemId != null) {
+ src = new StreamSource(reader, configuration.systemId);
+ } else {
+ src = new StreamSource(reader);
+ }
+ } else if (source != null) {
+ src = source;
+ }
+ xsr = factory.createXMLStreamReader(src);
+ try {
+ validator.validate(src);
+ } catch (final SAXException se) {
+ throw new XMLStreamException("Failed to create apply SCXML Validator", se);
+ }
+
+ } else {
+ // We can use the more direct XMLInputFactory API if validation isn't needed
+
+ if (urlStream != null) {
+ // systemId gets preference, then encoding if either are present
+ if (configuration.systemId != null) {
+ xsr = factory.createXMLStreamReader(configuration.systemId, urlStream);
+ } else if (configuration.encoding != null) {
+ xsr = factory.createXMLStreamReader(urlStream, configuration.encoding);
+ } else {
+ xsr = factory.createXMLStreamReader(urlStream);
+ }
+ } else if (reader != null) {
+ if (configuration.systemId != null) {
+ xsr = factory.createXMLStreamReader(configuration.systemId, reader);
+ } else {
+ xsr = factory.createXMLStreamReader(reader);
+ }
+ } else if (source != null) {
+ xsr = factory.createXMLStreamReader(source);
}
+
}
+
+ return xsr;
}
/**
- * Read the contents of this <state> element.
+ * @param input input string to check if null or empty after trim
+ * @return null if input is null or empty after trim()
+ */
+ private static String nullIfEmpty(final String input) {
+ return input == null || input.trim().length()==0 ? null : input.trim();
+ }
+
+ /**
+ * Parse the SCXML document supplied by the given {@link InputStream}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
- * @param parent The parent {@link TransitionalState} for this state (null for top level state).
+ * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
+ *
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
*
* @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readState(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
- final TransitionalState parent)
+ public static SCXML read(final InputStream scxmlStream)
throws IOException, ModelException, XMLStreamException {
- final State state = new State();
- state.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_STATE));
- final String initial = readAV(reader, SCXMLConstants.ATTR_INITIAL);
- if (initial != null) {
- state.setFirst(initial);
- }
- final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
- if (src != null) {
- String source = src;
- final Configuration copy = new Configuration(configuration);
- if (copy.parent == null) {
- copy.parent = scxml;
- }
- if (configuration.pathResolver != null) {
- source = configuration.pathResolver.resolvePath(src);
- copy.pathResolver = configuration.pathResolver.getResolver(src);
- }
- readTransitionalStateSrc(copy, source, state);
- }
-
- if (parent == null) {
- scxml.addChild(state);
- } else if (parent instanceof State) {
- ((State)parent).addChild(state);
- }
- else {
- ((Parallel)parent).addChild(state);
- }
- scxml.addTarget(state);
- if (configuration.parent != null) {
- configuration.parent.addTarget(state);
- }
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
- state.addTransition(readTransition(reader, configuration));
- } else if (SCXMLConstants.ELEM_STATE.equals(name)) {
- readState(reader, configuration, scxml, state);
- } else if (SCXMLConstants.ELEM_INITIAL.equals(name)) {
- readInitial(reader, configuration, state);
- } else if (SCXMLConstants.ELEM_FINAL.equals(name)) {
- readFinal(reader, configuration, scxml, state);
- } else if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
- readOnEntry(reader, configuration, state);
- } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
- readOnExit(reader, configuration, state);
- } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
- readParallel(reader, configuration, scxml, state);
- } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
- readDatamodel(reader, configuration, null, state);
- } else if (SCXMLConstants.ELEM_INVOKE.equals(name)) {
- readInvoke(reader, configuration, state);
- } else if (SCXMLConstants.ELEM_HISTORY.equals(name)) {
- readHistory(reader, configuration, scxml, state);
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_STATE, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_STATE, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
- }
+ return read(scxmlStream, new Configuration());
}
/**
- * Read the contents of this <parallel> element.
+ * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
- * @param parent The parent {@link TransitionalState} for this parallel (null for top level state).
+ * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
+ *
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
*
* @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
- final TransitionalState parent)
+ public static SCXML read(final InputStream scxmlStream, final Configuration configuration)
throws IOException, ModelException, XMLStreamException {
- final Parallel parallel = new Parallel();
- parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_PARALLEL));
- final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
- if (src != null) {
- String source = src;
- final Configuration copy = new Configuration(configuration);
- if (copy.parent == null) {
- copy.parent = scxml;
- }
- if (configuration.pathResolver != null) {
- source = configuration.pathResolver.resolvePath(src);
- copy.pathResolver = configuration.pathResolver.getResolver(src);
- }
- readTransitionalStateSrc(copy, source, parallel);
- }
-
- if (parent == null) {
- scxml.addChild(parallel);
- } else if (parent instanceof State) {
- ((State)parent).addChild(parallel);
- }
- else {
- ((Parallel)parent).addChild(parallel);
- }
- scxml.addTarget(parallel);
- if (configuration.parent != null) {
- configuration.parent.addTarget(parallel);
+ if (scxmlStream == null) {
+ throw new IllegalArgumentException(ERR_NULL_ISTR);
}
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
- parallel.addTransition(readTransition(reader, configuration));
- } else if (SCXMLConstants.ELEM_STATE.equals(name)) {
- readState(reader, configuration, scxml, parallel);
- } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
- readParallel(reader, configuration, scxml, parallel);
- } else if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
- readOnEntry(reader, configuration, parallel);
- } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
- readOnExit(reader, configuration, parallel);
- } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
- readDatamodel(reader, configuration, null, parallel);
- } else if (SCXMLConstants.ELEM_INVOKE.equals(name)) {
- readInvoke(reader, configuration, parallel);
- } else if (SCXMLConstants.ELEM_HISTORY.equals(name)) {
- readHistory(reader, configuration, scxml, parallel);
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
+ final SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null);
+ if (scxml != null) {
+ ModelUpdater.updateSCXML(scxml);
}
+ return scxml;
}
/**
- * Read the contents of this <final> element.
+ * Parse the SCXML document supplied by the given {@link Reader}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
- * @param parent The parent {@link State} for this final (null for top level state).
+ * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
+ *
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
*
* @throws IOException An IO error during parsing.
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
- final State parent)
- throws XMLStreamException, ModelException, IOException {
+ public static SCXML read(final Reader scxmlReader)
+ throws IOException, ModelException, XMLStreamException {
- final Final end = new Final();
- end.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_FINAL));
+ return read(scxmlReader, new Configuration());
+ }
- if (parent == null) {
- scxml.addChild(end);
- } else {
- parent.addChild(end);
- }
+ /**
+ * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}.
+ *
+ * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
+ *
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ */
+ public static SCXML read(final Reader scxmlReader, final Configuration configuration)
+ throws IOException, ModelException, XMLStreamException {
- scxml.addTarget(end);
- if (configuration.parent != null) {
- configuration.parent.addTarget(end);
+ if (scxmlReader == null) {
+ throw new IllegalArgumentException(ERR_NULL_READ);
}
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
- readOnEntry(reader, configuration, end);
- } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
- readOnExit(reader, configuration, end);
- } else if (SCXMLConstants.ELEM_DONEDATA.equals(name) && end.getDoneData() == null) {
- readDoneData(reader, configuration, end);
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
+ final SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null);
+ if (scxml != null) {
+ ModelUpdater.updateSCXML(scxml);
}
+ return scxml;
}
/**
- * Read the contents of this <donedata> element.
+ * Parse the SCXML document supplied by the given {@link Source}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param parent The parent {@link State} for this final (null for top level state).
+ * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readDoneData(final XMLStreamReader reader, final Configuration configuration, final Final parent)
- throws XMLStreamException, ModelException {
-
- final DoneData doneData = new DoneData();
- parent.setDoneData(doneData);
+ public static SCXML read(final Source scxmlSource)
+ throws IOException, ModelException, XMLStreamException {
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_PARAM.equals(name)) {
- if (doneData.getContent() == null) {
- readParam(reader, configuration, doneData);
- }
- else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
- }
- } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
- if (doneData.getParams().isEmpty()) {
- readContent(reader, configuration, doneData);
- }
- else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
- }
+ return read(scxmlSource, new Configuration());
}
/**
- * Parse the contents of the SCXML document that this "src" attribute value of a <state> or <parallel>
- * element points to. Without a URL fragment, the entire state machine is imported as contents of the
- * <state> or <parallel>. If a URL fragment is present, the fragment must specify the id of the
- * corresponding <state> or <parallel> to import.
+ * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}.
*
- * @param configuration The {@link Configuration} to use while parsing.
- * @param src The "src" attribute value.
- * @param ts The parent {@link TransitionalState} that specifies this "src" attribute.
+ * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
*
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readTransitionalStateSrc(final Configuration configuration, final String src,
- final TransitionalState ts)
- throws ModelException {
+ public static SCXML read(final Source scxmlSource, final Configuration configuration)
+ throws IOException, ModelException, XMLStreamException {
- // Check for URI fragment
- final String[] fragments = src.split("#", 2);
- final String location = fragments[0];
- String fragment = null;
- if (fragments.length > 1) {
- fragment = fragments[1];
+ if (scxmlSource == null) {
+ throw new IllegalArgumentException(ERR_NULL_SRC);
}
-
- // Parse external document
- SCXML externalSCXML;
- try {
- externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null);
- } catch (final Exception e) {
- final MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC);
- final String errMsg = msgFormat.format(new Object[] {src});
- throw new ModelException(errMsg + " : " + e.getMessage(), e);
+ final SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource);
+ if (scxml != null) {
+ ModelUpdater.updateSCXML(scxml);
}
+ return scxml;
+ }
- // Pull in the parts of the external document as needed
- if (fragment == null) {
- // All targets pulled in since its not a src fragment
- if (ts instanceof State) {
- final State s = (State) ts;
- final Initial ini = new Initial();
- final SimpleTransition t = new SimpleTransition();
- t.setNext(externalSCXML.getInitial());
- ini.setTransition(t);
- s.setInitial(ini);
- for (final EnterableState child : externalSCXML.getChildren()) {
- s.addChild(child);
- }
- s.setDatamodel(externalSCXML.getDatamodel());
- } else if (ts instanceof Parallel) {
- // TODO src attribute for <parallel>
- }
- } else {
- // Need to pull in only descendent targets
- final Object source = externalSCXML.getTargets().get(fragment);
- if (source == null) {
- final MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT);
- final String errMsg = msgFormat.format(new Object[] {src});
- throw new ModelException(errMsg);
- }
- if (source instanceof State && ts instanceof State) {
- final State s = (State) ts;
- final State include = (State) source;
- for (final OnEntry onentry : include.getOnEntries()) {
- s.addOnEntry(onentry);
- }
- for (final OnExit onexit : include.getOnExits()) {
- s.addOnExit(onexit);
- }
- s.setDatamodel(include.getDatamodel());
- final List<History> histories = include.getHistory();
- for (final History h : histories) {
- s.addHistory(h);
- configuration.parent.addTarget(h);
- }
- for (final EnterableState child : include.getChildren()) {
- s.addChild(child);
- configuration.parent.addTarget(child);
- readInExternalTargets(configuration.parent, child);
- }
- for (final Invoke invoke : include.getInvokes()) {
- s.addInvoke(invoke);
- }
- if (include.getInitial() != null) {
- s.setInitial(include.getInitial());
- }
- final List<Transition> transitions = include.getTransitionsList();
- for (final Transition t : transitions) {
- s.addTransition(t);
- }
- } else if (ts instanceof Parallel && source instanceof Parallel) {
- // TODO src attribute for <parallel>
- } else {
- final MessageFormat msgFormat =
- new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET);
- final String errMsg = msgFormat.format(new Object[] {src});
- throw new ModelException(errMsg);
- }
- }
- }
-
- /**
- * Add all the nested targets from given target to given parent state machine.
- *
- * @param parent The state machine
- * @param es The target to import
+ //------------------------- PUBLIC API METHODS -------------------------//
+ /*
+ * Public methods
*/
- private static void readInExternalTargets(final SCXML parent, final EnterableState es) {
- if (es instanceof TransitionalState) {
- for (final History h : ((TransitionalState)es).getHistory()) {
- parent.addTarget(h);
- }
- for (final EnterableState child : ((TransitionalState) es).getChildren()) {
- parent.addTarget(child);
- readInExternalTargets(parent, child);
- }
- }
- }
-
/**
- * Read the contents of this <datamodel> element.
+ * Parse the SCXML document at the supplied path.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
- * @param parent The parent {@link TransitionalState} for this datamodel (null for top level).
+ * @param scxmlPath The real path to the SCXML document.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration,
- final SCXML scxml, final TransitionalState parent)
- throws XMLStreamException, ModelException {
-
- final Datamodel dm = new Datamodel();
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_DATA.equals(name)) {
- readData(reader, configuration, dm);
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
- }
+ public static SCXML read(final String scxmlPath)
+ throws IOException, ModelException, XMLStreamException {
- if (parent == null) {
- scxml.setDatamodel(dm);
- } else {
- parent.setDatamodel(dm);
- }
+ return read(scxmlPath, new Configuration());
}
/**
- * Read the contents of this <data> element.
+ * Parse the SCXML document at the supplied path with the given {@link Configuration}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param dm The parent {@link Datamodel} for this data.
+ * @param scxmlPath The real path to the SCXML document.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
*
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm)
- throws XMLStreamException, ModelException {
-
- final Data datum = new Data();
- datum.setId(readRequiredAV(reader, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_ID));
- final String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
- final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
+ public static SCXML read(final String scxmlPath, final Configuration configuration)
+ throws IOException, ModelException, XMLStreamException {
- if (expr != null) {
- if (src != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
- }
- datum.setExpr(expr);
- skipToEndElement(reader);
- } else if (src != null ) {
- datum.setSrc(src);
- skipToEndElement(reader);
- } else {
- readParsedValue(reader, configuration, datum, false);
+ if (scxmlPath == null) {
+ throw new IllegalArgumentException(ERR_NULL_PATH);
}
- dm.addData(datum);
+ final SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null);
+ if (scxml != null) {
+ ModelUpdater.updateSCXML(scxml);
+ }
+ return scxml;
}
/**
- * Read the contents of this <invoke> element.
+ * Parse the SCXML document at the supplied {@link URL}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param parent The parent {@link TransitionalState} for this invoke.
+ * @param scxmlURL The SCXML document {@link URL} to parse.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
+ *
+ * @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readInvoke(final XMLStreamReader reader, final Configuration configuration,
- final TransitionalState parent)
- throws XMLStreamException, ModelException {
-
- final Invoke invoke = new Invoke();
- invoke.setId(readAV(reader, SCXMLConstants.ATTR_ID));
- invoke.setIdlocation(readAV(reader, SCXMLConstants.ATTR_IDLOCATION));
- invoke.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
- invoke.setSrcexpr(readAV(reader, SCXMLConstants.ATTR_SRCEXPR));
- invoke.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
- invoke.setAutoForward(readBooleanAV(reader, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.ATTR_AUTOFORWARD));
- invoke.setNamelist(readAV(reader, SCXMLConstants.ATTR_NAMELIST));
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_PARAM.equals(name)) {
- readParam(reader, configuration, invoke);
- } else if (SCXMLConstants.ELEM_FINALIZE.equals(name)) {
- readFinalize(reader, configuration, parent, invoke);
- } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
- readContent(reader, configuration, invoke);
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
- }
+ public static SCXML read(final URL scxmlURL)
+ throws IOException, ModelException, XMLStreamException {
- parent.addInvoke(invoke);
+ return read(scxmlURL, new Configuration());
}
/**
- * Read the contents of this <param> element.
+ * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param.
+ * @param scxmlURL The SCXML document {@link URL} to parse.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
+ *
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
*
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readParam(final XMLStreamReader reader, final Configuration configuration,
- final ParamsContainer parent)
- throws XMLStreamException, ModelException {
+ public static SCXML read(final URL scxmlURL, final Configuration configuration)
+ throws IOException, ModelException, XMLStreamException {
- final Param param = new Param();
- param.setName(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_NAME));
- final String location = readAV(reader, SCXMLConstants.ATTR_LOCATION);
- final String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
- if (expr != null) {
- if (location != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION, SCXMLConstants.ATTR_EXPR);
- }
- else {
- param.setExpr(expr);
- }
- }
- else if (location == null) {
- // force error missing required location or expr: use location attr for this
- param.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION));
+ if (scxmlURL == null) {
+ throw new IllegalArgumentException(ERR_NULL_URL);
}
- else {
- param.setLocation(location);
+ final SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null);
+ if (scxml != null) {
+ ModelUpdater.updateSCXML(scxml);
}
- parent.getParams().add(param);
- skipToEndElement(reader);
+ return scxml;
}
/**
- * Read the contents of this <finalize> element.
+ * Read the contents of this <assign> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param state The {@link TransitionalState} which contains the parent {@link Invoke}.
- * @param invoke The parent {@link Invoke} for this finalize.
+ * @param executable The parent {@link Executable} for this action.
+ * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readFinalize(final XMLStreamReader reader, final Configuration configuration,
- final TransitionalState state, final Invoke invoke)
+ private static void readAssign(final XMLStreamReader reader, final Configuration configuration,
+ final Executable executable, final ActionsContainer parent)
throws XMLStreamException, ModelException {
- final Finalize finalize = new Finalize();
- readExecutableContext(reader, configuration, finalize, null);
- invoke.setFinalize(finalize);
- finalize.setParent(state);
- }
-
- /**
- * Read the contents of this <content> element.
- *
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param contentContainer The {@link ContentContainer} for this content.
- *
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- private static void readContent(final XMLStreamReader reader, final Configuration configuration,
- final ContentContainer contentContainer)
- throws XMLStreamException, ModelException {
-
- final Content content = new Content();
- content.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
- if (content.getExpr() != null) {
+ final Assign assign = new Assign();
+ assign.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
+ assign.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_LOCATION));
+ assign.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
+ if (assign.getExpr() != null && assign.getSrc() != null) {
+ reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
+ }
+ else if (assign.getExpr() != null) {
+ skipToEndElement(reader);
+ }
+ else if (assign.getSrc() != null) {
skipToEndElement(reader);
+ String resolvedSrc = assign.getSrc();
+ if (configuration.pathResolver != null) {
+ resolvedSrc = configuration.pathResolver.resolvePath(resolvedSrc);
+ }
+ try {
+ assign.setParsedValue(ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc));
+ } catch (final IOException e) {
+ throw new ModelException(e);
+ }
}
else {
- readParsedValue(reader, configuration, content, contentContainer instanceof Invoke);
+ final Location location = reader.getLocation();
+ readParsedValue(reader, configuration, assign, false);
+ if (assign.getParsedValue() == null) {
+ // report missing expression (as most common use-case)
+ reportMissingAttribute(location, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR);
+ }
+ }
+
+ assign.setParent(executable);
+ if (parent != null) {
+ parent.addAction(assign);
+ } else {
+ executable.addAction(assign);
}
- contentContainer.setContent(content);
}
/**
- * Read the contents of this <initial> element.
+ * Gets the attribute value at the current reader location.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param state The parent composite {@link State} for this initial.
+ * @param attrLocalName The attribute name whose value is needed.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
+ * @return The value of the attribute.
*/
- private static void readInitial(final XMLStreamReader reader, final Configuration configuration,
- final State state)
- throws XMLStreamException, ModelException {
-
- final Initial initial = new Initial();
-
- loop : while (reader.hasNext()) {
- String name, nsURI;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
- initial.setTransition(readSimpleTransition(reader, configuration));
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- break loop;
- default:
- }
- }
-
- state.setInitial(initial);
+ private static String readAV(final XMLStreamReader reader, final String attrLocalName) {
+ return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
}
/**
- * Read the contents of this <history> element.
+ * Read the following body contents into a String.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param scxml The root of the object model being parsed.
- * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history.
+ *
+ * @return The body content read into a String.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readHistory(final XMLStreamReader reader, final Configuration configuration,
- final SCXML scxml, final TransitionalState ts)
- throws XMLStreamException, ModelException {
-
- final History history = new History();
- history.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_HISTORY));
- history.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
+ private static String readBody(final XMLStreamReader reader)
+ throws XMLStreamException {
- ts.addHistory(history);
- scxml.addTarget(history);
+ final StringBuilder body = new StringBuilder();
+ // Add all body content to StringBuilder
loop : while (reader.hasNext()) {
- String name, nsURI;
switch (reader.next()) {
case XMLStreamConstants.START_ELEMENT:
- nsURI = reader.getNamespaceURI();
- name = reader.getLocalName();
- if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
- history.setTransition(readTransition(reader, configuration));
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
- }
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
- }
+ logger.warn("Ignoring XML content in <script> element, encountered element: "
+ + createQualifiedName(reader.getPrefix(), reader.getLocalName()));
+ skipToEndElement(reader);
+ break;
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ case XMLStreamConstants.CDATA:
+ body.append(reader.getText());
+ break;
+ case XMLStreamConstants.COMMENT:
break;
case XMLStreamConstants.END_ELEMENT:
break loop;
- default:
+ default: // rest is ignored
}
}
+ return body.toString();
}
/**
- * Read the contents of this <onentry> element.
+ * Gets the Boolean attribute value at the current reader location.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param es The parent {@link EnterableState} for this onentry.
+ * @param elementName The name of the element for which the attribute value is needed.
+ * @param attrLocalName The attribute name whose value is needed.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
+ * @return The Boolean value of the attribute.
+ * @throws ModelException When the attribute value is not empty but neither "true" or "false".
*/
- private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration,
- final EnterableState es)
- throws XMLStreamException, ModelException {
-
- final OnEntry onentry = new OnEntry();
- onentry.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONENTRY, SCXMLConstants.ATTR_EVENT));
- readExecutableContext(reader, configuration, onentry, null);
- es.addOnEntry(onentry);
+ private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName,
+ final String attrLocalName)
+ throws ModelException {
+ final String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
+ final Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
+ if (result == null && value != null) {
+ final MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN);
+ final String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()});
+ throw new ModelException(errMsg);
+ }
+ return result;
}
/**
- * Read the contents of this <onexit> element.
+ * Read the contents of this <cancel> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param es The parent {@link EnterableState} for this onexit.
+ * @param executable The parent {@link Executable} for this action.
+ * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readOnExit(final XMLStreamReader reader, final Configuration configuration,
- final EnterableState es)
+ private static void readCancel(final XMLStreamReader reader, final Configuration configuration,
+ final Executable executable, final ActionsContainer parent)
throws XMLStreamException, ModelException {
- final OnExit onexit = new OnExit();
- onexit.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONEXIT, SCXMLConstants.ATTR_EVENT));
- readExecutableContext(reader, configuration, onexit, null);
- es.addOnExit(onexit);
+ final Cancel cancel = new Cancel();
+ cancel.setSendid(readAV(reader, SCXMLConstants.ATTR_SENDID));
+ final String attrValue = readAV(reader, SCXMLConstants.ATTR_SENDIDEXPR);
+ if (attrValue != null) {
+ if (cancel.getSendid() != null) {
+ reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_CANCEL, SCXMLConstants.ATTR_SENDID, SCXMLConstants.ATTR_SENDIDEXPR);
+ }
+ else {
+ cancel.setSendidexpr(attrValue);
+ }
+ }
+ cancel.setParent(executable);
+ if (parent != null) {
+ parent.addAction(cancel);
+ } else {
+ executable.addAction(cancel);
+ }
+ skipToEndElement(reader);
}
/**
- * Read the contents of this simple <transition> element.
+ * Read the contents of this <content> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
+ * @param contentContainer The {@link ContentContainer} for this content.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static SimpleTransition readSimpleTransition(final XMLStreamReader reader, final Configuration configuration)
+ private static void readContent(final XMLStreamReader reader, final Configuration configuration,
+ final ContentContainer contentContainer)
throws XMLStreamException, ModelException {
- final SimpleTransition transition = new SimpleTransition();
- transition.setNext(readAV(reader, SCXMLConstants.ATTR_TARGET));
- final String type = readAV(reader, SCXMLConstants.ATTR_TYPE);
- if (type != null) {
- try {
- transition.setType(TransitionType.valueOf(type));
- }
- catch (final IllegalArgumentException e) {
- final MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
- final String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
- throw new ModelException(errMsg);
- }
+ final Content content = new Content();
+ content.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
+ if (content.getExpr() != null) {
+ skipToEndElement(reader);
}
-
- readExecutableContext(reader, configuration, transition, null);
-
- return transition;
+ else {
+ readParsedValue(reader, configuration, content, contentContainer instanceof Invoke);
+ }
+ contentContainer.setContent(content);
}
/**
- * Read the contents of this <transition> element.
+ * Read the contents of this custom action.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
+ * @param customAction The {@link CustomAction} to read.
+ * @param executable The parent {@link Executable} for this custom action.
+ * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static Transition readTransition(final XMLStreamReader reader, final Configuration configuration)
+ private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
+ final CustomAction customAction, final Executable executable,
+ final ActionsContainer parent)
throws XMLStreamException, ModelException {
- final Transition transition = new Transition();
- transition.setCond(readAV(reader, SCXMLConstants.ATTR_COND));
- transition.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
- transition.setNext(readAV(reader, SCXMLConstants.ATTR_TARGET));
- final String type = readAV(reader, SCXMLConstants.ATTR_TYPE);
- if (type != null) {
+ // Instantiate custom action
+ Object actionObject;
+ final String className = customAction.getActionClass().getName();
+ ClassLoader cl = configuration.customActionClassLoader;
+ if (configuration.useContextClassLoaderForCustomActions) {
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ if (cl == null) {
+ cl = SCXMLReader.class.getClassLoader();
+ }
+ Class<?> clazz;
+ try {
+ clazz = cl.loadClass(className);
+ actionObject = clazz.getConstructor().newInstance();
+ } catch (final ClassNotFoundException cnfe) {
+ throw new XMLStreamException("Cannot find custom action class:" + className, cnfe);
+ } catch (final IllegalAccessException iae) {
+ throw new XMLStreamException("Cannot access custom action class:" + className, iae);
+ } catch (final ReflectiveOperationException ie) {
+ throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie);
+ }
+ if (!(actionObject instanceof Action)) {
+ throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className);
+ }
+
+ // Set the attribute values as properties
+ final Action action = (Action) actionObject;
+
+ final CustomActionWrapper actionWrapper = new CustomActionWrapper();
+ actionWrapper.setAction(action);
+ actionWrapper.setPrefix(reader.getPrefix());
+ actionWrapper.setLocalName(reader.getLocalName());
+ final Map<String, String> namespaces = readNamespaces(reader);
+ if (namespaces != null) {
+ actionWrapper.getNamespaces().putAll(namespaces);
+ }
+
+ final Map<String, String> attributes = new HashMap<>();
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ final String name = reader.getAttributeLocalName(i);
+ final String qname = createQualifiedName(reader.getAttributePrefix(i), name);
+ final String value = reader.getAttributeValue(i);
+ attributes.put(qname, value);
+ final String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
+ Method method;
try {
- transition.setType(TransitionType.valueOf(type));
- }
- catch (final IllegalArgumentException e) {
- final MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
- final String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
- throw new ModelException(errMsg);
+ method = clazz.getMethod(setter, String.class);
+ method.invoke(action, value);
+ } catch (final NoSuchMethodException nsme) {
+ logger.warn("No method: " + setter + "(String) found in custom action class: " + className+ " for "
+ + qname + "=\"" + value + "\". Attribute ignored");
+ } catch (final InvocationTargetException ite) {
+ throw new XMLStreamException("Exception calling method:" + setter + "(String) in custom action class:"
+ + className, ite);
+ } catch (final IllegalAccessException iae) {
+ throw new XMLStreamException("Cannot access method: " + setter +"(String) in custom action class: "
+ + className, iae);
}
}
+ if (!attributes.isEmpty()) {
+ actionWrapper.setAttributes(attributes);
+ }
- readExecutableContext(reader, configuration, transition, null);
+ // Add any body content if necessary
+ if (action instanceof ParsedValueContainer) {
+ readParsedValue(reader, configuration, (ParsedValueContainer)action, false);
+ }
+ else {
+ skipToEndElement(reader);
+ }
- return transition;
+ // Wire in the action and add to parent
+ actionWrapper.setParent(executable);
+ if (parent != null) {
+ parent.addAction(actionWrapper);
+ } else {
+ executable.addAction(actionWrapper);
+ }
}
/**
- * Read this set of executable content elements.
+ * Read the contents of this <data> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} to which this content belongs.
- * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action.
+ * @param dm The parent {@link Datamodel} for this data.
+ *
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ */
+ private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm)
+ throws XMLStreamException, ModelException {
+
+ final Data datum = new Data();
+ datum.setId(readRequiredAV(reader, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_ID));
+ final String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
+ final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
+
+ if (expr != null) {
+ if (src != null) {
+ reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_DATA, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
+ }
+ datum.setExpr(expr);
+ skipToEndElement(reader);
+ } else if (src != null ) {
+ datum.setSrc(src);
+ skipToEndElement(reader);
+ } else {
+ readParsedValue(reader, configuration, datum, false);
+ }
+ dm.addData(datum);
+ }
+
+ /**
+ * Read the contents of this <datamodel> element.
+ *
+ * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param scxml The root of the object model being parsed.
+ * @param parent The parent {@link TransitionalState} for this datamodel (null for top level).
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
*/
- private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration,
+ final SCXML scxml, final TransitionalState parent)
throws XMLStreamException, ModelException {
- String end = "";
- if (parent != null) {
- end = parent instanceof If ? SCXMLConstants.ELEM_IF : SCXMLConstants.ELEM_FOREACH;
- } else if (executable instanceof SimpleTransition) {
- end = SCXMLConstants.ELEM_TRANSITION;
- } else if (executable instanceof OnEntry) {
- end = SCXMLConstants.ELEM_ONENTRY;
- } else if (executable instanceof OnExit) {
- end = SCXMLConstants.ELEM_ONEXIT;
- } else if (executable instanceof Finalize) {
- end = SCXMLConstants.ELEM_FINALIZE;
- }
+ final Datamodel dm = new Datamodel();
loop : while (reader.hasNext()) {
String name, nsURI;
@@ -1464,58 +1259,13 @@ public final class SCXMLReader {
nsURI = reader.getNamespaceURI();
name = reader.getLocalName();
if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_RAISE.equals(name)) {
- if (executable instanceof Finalize) {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
- } else {
- readRaise(reader, configuration, executable, parent);
- }
- } else if (SCXMLConstants.ELEM_FOREACH.equals(name)) {
- readForeach(reader, configuration, executable, parent);
- } else if (SCXMLConstants.ELEM_IF.equals(name)) {
- readIf(reader, configuration, executable, parent);
- } else if (SCXMLConstants.ELEM_LOG.equals(name)) {
- readLog(reader, executable, parent);
- } else if (SCXMLConstants.ELEM_ASSIGN.equals(name)) {
- readAssign(reader, configuration, executable, parent);
- } else if (SCXMLConstants.ELEM_SEND.equals(name)) {
- if (executable instanceof Finalize) {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
- } else {
- readSend(reader, configuration, executable, parent);
- }
- } else if (SCXMLConstants.ELEM_CANCEL.equals(name)) {
- readCancel(reader, configuration, executable, parent);
- } else if (SCXMLConstants.ELEM_SCRIPT.equals(name)) {
- readScript(reader, configuration, executable, parent);
- } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSEIF.equals(name)) {
- readElseIf(reader, executable, (If) parent);
- } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSE.equals(name)) {
- readElse(reader, executable, (If)parent);
- } else {
- reportIgnoredElement(reader, configuration, end, nsURI, name);
- }
- } else if (SCXMLConstants.XMLNS_COMMONS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_VAR.equals(name)) {
- readCustomAction(reader, configuration, Var.CUSTOM_ACTION, executable, parent);
- } else {
- reportIgnoredElement(reader, configuration, end, nsURI, name);
- }
- } else { // custom action
- CustomAction customAction = null;
- if (!configuration.customActions.isEmpty()) {
- for (final CustomAction ca : configuration.customActions) {
- if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) {
- customAction = ca;
- break;
- }
- }
- }
- if (customAction != null) {
- readCustomAction(reader, configuration, customAction, executable, parent);
+ if (SCXMLConstants.ELEM_DATA.equals(name)) {
+ readData(reader, configuration, dm);
} else {
- reportIgnoredElement(reader, configuration, end, nsURI, name);
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
}
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DATAMODEL, nsURI, name);
}
break;
case XMLStreamConstants.END_ELEMENT:
@@ -1523,87 +1273,196 @@ public final class SCXMLReader {
default:
}
}
+
+ if (parent == null) {
+ scxml.setDatamodel(dm);
+ } else {
+ parent.setDatamodel(dm);
+ }
}
+ /*
+ * Private utility functions for reading the SCXML document.
+ */
/**
- * Read the contents of this <raise> element.
+ * Read the SCXML document through the {@link XMLStreamReader}.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
*
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
+ * (not wired up to be immediately usable).
+ *
+ * @throws IOException An IO error during parsing.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readRaise(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
- throws XMLStreamException, ModelException {
+ private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration)
+ throws IOException, ModelException, XMLStreamException {
- if (executable instanceof Finalize) {
- // https://www.w3.org/TR/2015/REC-scxml-20150901/#finalize
- // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
- // In particular, the <send> and <raise> elements MUST NOT occur.
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_RAISE);
- }
- else {
- final Raise raise = new Raise();
- raise.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
- raise.setParent(executable);
- if (parent != null) {
- parent.addAction(raise);
- } else {
- executable.addAction(raise);
+ final SCXML scxml = new SCXML();
+ scxml.setPathResolver(configuration.pathResolver);
+ while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_SCXML.equals(name)) {
+ readSCXML(reader, configuration, scxml);
+ } else {
+ reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
+ }
+ break;
+ case XMLStreamConstants.NAMESPACE:
+ System.err.println(reader.getNamespaceCount());
+ break;
+ default:
}
- skipToEndElement(reader);
}
+ return scxml;
}
/**
- * Read the contents of this <if> element.
+ * Read the contents of this <donedata> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this <if> is a child of one.
+ * @param parent The parent {@link State} for this final (null for top level state).
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
* @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
* errors in the SCXML document that may not be identified by the schema).
*/
- private static void readIf(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readDoneData(final XMLStreamReader reader, final Configuration configuration, final Final parent)
throws XMLStreamException, ModelException {
- final If iff = new If();
- iff.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_IF, SCXMLConstants.ATTR_COND));
- iff.setParent(executable);
- if (parent != null) {
- parent.addAction(iff);
- } else {
- executable.addAction(iff);
+ final DoneData doneData = new DoneData();
+ parent.setDoneData(doneData);
+
+ loop : while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_PARAM.equals(name)) {
+ if (doneData.getContent() == null) {
+ readParam(reader, configuration, doneData);
+ }
+ else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
+ }
+ } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
+ if (doneData.getParams().isEmpty()) {
+ readContent(reader, configuration, doneData);
+ }
+ else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_DONEDATA, nsURI, name);
+ }
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ break loop;
+ default:
+ }
}
- readExecutableContext(reader, configuration, executable, iff);
}
/**
- * Read the contents of this <elseif> element.
+ * Read the current element into a DOM {@link Element}.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param executable The parent {@link Executable} for this action.
- * @param iff The parent {@link If} for this <elseif>.
+ *
+ * @return The parsed content as a DOM {@link Element}.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void readElseIf(final XMLStreamReader reader, final Executable executable, final If iff)
- throws XMLStreamException, ModelException {
+ private static Element readElement(final XMLStreamReader reader)
+ throws XMLStreamException {
- final ElseIf elseif = new ElseIf();
- elseif.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_ELSEIF, SCXMLConstants.ATTR_COND));
- elseif.setParent(executable);
- iff.addAction(elseif);
- skipToEndElement(reader);
+ // Create a document in which to build the DOM node
+ Document document;
+ try {
+ document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ } catch (final ParserConfigurationException pce) {
+ throw new XMLStreamException(ERR_PARSER_CFG);
+ }
+
+ // This root element will be returned, add any attributes as specified
+ final Element root = document.createElementNS(reader.getNamespaceURI(), createQualifiedName(reader.getPrefix(), reader.getLocalName()));
+ document.appendChild(root);
+
+ boolean children = false, cdata = false;
+ Node parent = root;
+
+ // Convert stream to DOM node(s) while maintaining parent child relationships
+ loop : while (reader.hasNext()) {
+ Node child = null;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ if (!children && root.hasChildNodes()) {
+ // remove any children
+ root.setTextContent(null);
+ }
+ children = true;
+ final Element elem = document.createElementNS(reader.getNamespaceURI(),
+ createQualifiedName(reader.getPrefix(), reader.getLocalName()));
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ final Attr attr = document.createAttributeNS(reader.getAttributeNamespace(i),
+ createQualifiedName(reader.getAttributePrefix(i), reader.getAttributeLocalName(i)));
+ attr.setValue(reader.getAttributeValue(i));
+ elem.setAttributeNodeNS(attr);
+ }
+ parent.appendChild(elem);
+ parent = elem;
+ break;
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ if (!children || parent != root) {
+ child = document.createTextNode(reader.getText());
+ }
+ break;
+ case XMLStreamConstants.CDATA:
+ if (!children || parent != root) {
+ cdata = true;
+ child = document.createCDATASection(reader.getText());
+ }
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ parent = parent.getParentNode();
+ if (parent == document) {
+ break loop;
+ }
+ break;
+ default: // rest is ignored
+ }
+ if (child != null) {
+ parent.appendChild(child);
+ }
+ }
+ if (!children && root.hasChildNodes() && root.getChildNodes().getLength() > 1) {
+ final String text = root.getTextContent().trim();
+ if (!cdata) {
+ root.setTextContent(text);
+ } else {
+ root.setTextContent(null);
+ root.appendChild(document.createCDATASection(text));
+ }
+ }
+ return root;
}
/**
@@ -1625,187 +1484,51 @@ public final class SCXMLReader {
}
/**
- * Read the contents of this <foreach> element.
+ * Read the contents of this <elseif> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
* @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this <foreach> is a child of one.
+ * @param iff The parent {@link If} for this <elseif>.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readForeach(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readElseIf(final XMLStreamReader reader, final Executable executable, final If iff)
throws XMLStreamException, ModelException {
- final Foreach fe = new Foreach();
- fe.setArray(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ARRAY));
- fe.setItem(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ITEM));
- fe.setIndex(readAV(reader, SCXMLConstants.ATTR_INDEX));
- fe.setParent(executable);
- if (parent != null) {
- parent.addAction(fe);
- } else {
- executable.addAction(fe);
- }
- readExecutableContext(reader, configuration, executable, fe);
- }
-
- /**
- * Read the contents of this <log> element.
- *
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
- *
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- */
- private static void readLog(final XMLStreamReader reader, final Executable executable, final ActionsContainer parent)
- throws XMLStreamException {
-
- final Log log = new Log();
- log.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
- log.setLabel(readAV(reader, SCXMLConstants.ATTR_LABEL));
- log.setParent(executable);
- if (parent != null) {
- parent.addAction(log);
- } else {
- executable.addAction(log);
- }
+ final ElseIf elseif = new ElseIf();
+ elseif.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_ELSEIF, SCXMLConstants.ATTR_COND));
+ elseif.setParent(executable);
+ iff.addAction(elseif);
skipToEndElement(reader);
}
/**
- * Read the contents of this <assign> element.
+ * Read this set of executable content elements.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
+ * @param executable The parent {@link Executable} to which this content belongs.
+ * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readAssign(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration,
+ final Executable executable, final ActionsContainer parent)
throws XMLStreamException, ModelException {
- final Assign assign = new Assign();
- assign.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
- assign.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_LOCATION));
- assign.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
- if (assign.getExpr() != null && assign.getSrc() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR, SCXMLConstants.ATTR_SRC);
- }
- else if (assign.getExpr() != null) {
- skipToEndElement(reader);
- }
- else if (assign.getSrc() != null) {
- skipToEndElement(reader);
- String resolvedSrc = assign.getSrc();
- if (configuration.pathResolver != null) {
- resolvedSrc = configuration.pathResolver.resolvePath(resolvedSrc);
- }
- try {
- assign.setParsedValue(ContentParser.DEFAULT_PARSER.parseResource(resolvedSrc));
- } catch (final IOException e) {
- throw new ModelException(e);
- }
- }
- else {
- final Location location = reader.getLocation();
- readParsedValue(reader, configuration, assign, false);
- if (assign.getParsedValue() == null) {
- // report missing expression (as most common use-case)
- reportMissingAttribute(location, SCXMLConstants.ELEM_ASSIGN, SCXMLConstants.ATTR_EXPR);
- }
- }
-
- assign.setParent(executable);
+ String end = "";
if (parent != null) {
- parent.addAction(assign);
- } else {
- executable.addAction(assign);
- }
- }
-
- /**
- * Read the contents of this <send> element.
- *
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
- *
- * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
- */
- private static void readSend(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
- throws XMLStreamException, ModelException {
-
- if (executable instanceof Finalize) {
- // https://www.w3.org/TR/2015/REC-scxml-20150901/#finalize
- // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
- // In particular, the <send> and <raise> elements MUST NOT occur.
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, SCXMLConstants.XMLNS_SCXML, SCXMLConstants.ELEM_SEND);
- return;
- }
-
- final Send send = new Send();
- send.setId(readAV(reader, SCXMLConstants.ATTR_ID));
- String attrValue = readAV(reader, SCXMLConstants.ATTR_IDLOCATION);
- if (attrValue != null) {
- if (send.getId() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_ID, SCXMLConstants.ATTR_IDLOCATION);
- }
- else {
- send.setIdlocation(attrValue);
- }
- }
- send.setDelay(readAV(reader, SCXMLConstants.ATTR_DELAY));
- attrValue = readAV(reader, SCXMLConstants.ATTR_DELAYEXPR);
- if (attrValue != null) {
- if (send.getDelay() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_DELAY, SCXMLConstants.ATTR_DELAYEXPR);
- }
- else {
- send.setDelayexpr(attrValue);
- }
- }
- send.setEvent(readAV(reader, SCXMLConstants.ATTR_EVENT));
- attrValue = readAV(reader, SCXMLConstants.ATTR_EVENTEXPR);
- if (attrValue != null) {
- if (send.getEvent() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_EVENT, SCXMLConstants.ATTR_EVENTEXPR);
- }
- else {
- send.setEventexpr(attrValue);
- }
- }
- send.setHints(readAV(reader, SCXMLConstants.ATTR_HINTS));
- send.setNamelist(readAV(reader, SCXMLConstants.ATTR_NAMELIST));
- send.setTarget(readAV(reader, SCXMLConstants.ATTR_TARGET));
- attrValue = readAV(reader, SCXMLConstants.ATTR_TARGETEXPR);
- if (attrValue != null) {
- if (send.getTarget() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_TARGET, SCXMLConstants.ATTR_TARGETEXPR);
- }
- else {
- send.setTargetexpr(attrValue);
- }
- }
- send.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
- attrValue = readAV(reader, SCXMLConstants.ATTR_TYPEEXPR);
- if (attrValue != null) {
- if (send.getType() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_SEND, SCXMLConstants.ATTR_TYPE, SCXMLConstants.ATTR_TYPEEXPR);
- }
- else {
- send.setTypeexpr(attrValue);
- }
+ end = parent instanceof If ? SCXMLConstants.ELEM_IF : SCXMLConstants.ELEM_FOREACH;
+ } else if (executable instanceof SimpleTransition) {
+ end = SCXMLConstants.ELEM_TRANSITION;
+ } else if (executable instanceof OnEntry) {
+ end = SCXMLConstants.ELEM_ONENTRY;
+ } else if (executable instanceof OnExit) {
+ end = SCXMLConstants.ELEM_ONEXIT;
+ } else if (executable instanceof Finalize) {
+ end = SCXMLConstants.ELEM_FINALIZE;
}
loop : while (reader.hasNext()) {
@@ -1815,25 +1538,58 @@ public final class SCXMLReader {
nsURI = reader.getNamespaceURI();
name = reader.getLocalName();
if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
- if (SCXMLConstants.ELEM_PARAM.equals(name)) {
- if (send.getContent() == null) {
- readParam(reader, configuration, send);
- }
- else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
+ if (SCXMLConstants.ELEM_RAISE.equals(name)) {
+ if (executable instanceof Finalize) {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
+ } else {
+ readRaise(reader, configuration, executable, parent);
}
- } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
- if (send.getNamelist() == null && send.getParams().isEmpty()) {
- readContent(reader, configuration, send);
+ } else if (SCXMLConstants.ELEM_FOREACH.equals(name)) {
+ readForeach(reader, configuration, executable, parent);
+ } else if (SCXMLConstants.ELEM_IF.equals(name)) {
+ readIf(reader, configuration, executable, parent);
+ } else if (SCXMLConstants.ELEM_LOG.equals(name)) {
+ readLog(reader, executable, parent);
+ } else if (SCXMLConstants.ELEM_ASSIGN.equals(name)) {
+ readAssign(reader, configuration, executable, parent);
+ } else if (SCXMLConstants.ELEM_SEND.equals(name)) {
+ if (executable instanceof Finalize) {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINALIZE, nsURI, name);
+ } else {
+ readSend(reader, configuration, executable, parent);
}
- else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
+ } else if (SCXMLConstants.ELEM_CANCEL.equals(name)) {
+ readCancel(reader, configuration, executable, parent);
+ } else if (SCXMLConstants.ELEM_SCRIPT.equals(name)) {
+ readScript(reader, configuration, executable, parent);
+ } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSEIF.equals(name)) {
+ readElseIf(reader, executable, (If) parent);
+ } else if (SCXMLConstants.ELEM_IF.equals(end) && SCXMLConstants.ELEM_ELSE.equals(name)) {
+ readElse(reader, executable, (If)parent);
+ } else {
+ reportIgnoredElement(reader, configuration, end, nsURI, name);
+ }
+ } else if (SCXMLConstants.XMLNS_COMMONS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_VAR.equals(name)) {
+ readCustomAction(reader, configuration, Var.CUSTOM_ACTION, executable, parent);
+ } else {
+ reportIgnoredElement(reader, configuration, end, nsURI, name);
+ }
+ } else { // custom action
+ CustomAction customAction = null;
+ if (!configuration.customActions.isEmpty()) {
+ for (final CustomAction ca : configuration.customActions) {
+ if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) {
+ customAction = ca;
+ break;
+ }
}
+ }
+ if (customAction != null) {
+ readCustomAction(reader, configuration, customAction, executable, parent);
} else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
+ reportIgnoredElement(reader, configuration, end, nsURI, name);
}
- } else {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_SEND, nsURI, name);
}
break;
case XMLStreamConstants.END_ELEMENT:
@@ -1841,70 +1597,115 @@ public final class SCXMLReader {
default:
}
}
+ }
- send.setParent(executable);
- if (parent != null) {
- parent.addAction(send);
+ /**
+ * Read the contents of this <final> element.
+ *
+ * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param scxml The root of the object model being parsed.
+ * @param parent The parent {@link State} for this final (null for top level state).
+ *
+ * @throws IOException An IO error during parsing.
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
+ */
+ private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
+ final State parent)
+ throws XMLStreamException, ModelException, IOException {
+
+ final Final end = new Final();
+ end.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_FINAL));
+
+ if (parent == null) {
+ scxml.addChild(end);
} else {
- executable.addAction(send);
+ parent.addChild(end);
+ }
+
+ scxml.addTarget(end);
+ if (configuration.parent != null) {
+ configuration.parent.addTarget(end);
+ }
+
+ loop : while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
+ readOnEntry(reader, configuration, end);
+ } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
+ readOnExit(reader, configuration, end);
+ } else if (SCXMLConstants.ELEM_DONEDATA.equals(name) && end.getDoneData() == null) {
+ readDoneData(reader, configuration, end);
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_FINAL, nsURI, name);
+ }
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ break loop;
+ default:
+ }
}
}
/**
- * Read the contents of this <cancel> element.
+ * Read the contents of this <finalize> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
+ * @param state The {@link TransitionalState} which contains the parent {@link Invoke}.
+ * @param invoke The parent {@link Invoke} for this finalize.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readCancel(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readFinalize(final XMLStreamReader reader, final Configuration configuration,
+ final TransitionalState state, final Invoke invoke)
throws XMLStreamException, ModelException {
- final Cancel cancel = new Cancel();
- cancel.setSendid(readAV(reader, SCXMLConstants.ATTR_SENDID));
- final String attrValue = readAV(reader, SCXMLConstants.ATTR_SENDIDEXPR);
- if (attrValue != null) {
- if (cancel.getSendid() != null) {
- reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_CANCEL, SCXMLConstants.ATTR_SENDID, SCXMLConstants.ATTR_SENDIDEXPR);
- }
- else {
- cancel.setSendidexpr(attrValue);
- }
- }
- cancel.setParent(executable);
- if (parent != null) {
- parent.addAction(cancel);
- } else {
- executable.addAction(cancel);
- }
- skipToEndElement(reader);
+ final Finalize finalize = new Finalize();
+ readExecutableContext(reader, configuration, finalize, null);
+ invoke.setFinalize(finalize);
+ finalize.setParent(state);
}
/**
- * Read the contents of this <script> element.
+ * Read the contents of this <foreach> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
* @param executable The parent {@link Executable} for this action.
- * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
+ * @param parent The optional parent {@link ActionsContainer} if this <foreach> is a child of one.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readScript(final XMLStreamReader reader, final Configuration configuration,
- final Executable executable, final ActionsContainer parent)
+ private static void readForeach(final XMLStreamReader reader, final Configuration configuration,
+ final Executable executable, final ActionsContainer parent)
throws XMLStreamException, ModelException {
- final Script script = readScript(reader, configuration);
- script.setParent(executable);
+ final Foreach fe = new Foreach();
+ fe.setArray(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ARRAY));
+ fe.setItem(readRequiredAV(reader, SCXMLConstants.ELEM_FOREACH, SCXMLConstants.ATTR_ITEM));
+ fe.setIndex(readAV(reader, SCXMLConstants.ATTR_INDEX));
+ fe.setParent(executable);
if (parent != null) {
- parent.addAction(script);
+ parent.addAction(fe);
} else {
- executable.addAction(script);
+ executable.addAction(fe);
}
+ readExecutableContext(reader, configuration, executable, fe);
}
/**
@@ -1928,395 +1729,310 @@ public final class SCXMLReader {
}
/**
- * Read the contents of this <script> element.
+ * Read the contents of this <history> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
+ * @param scxml The root of the object model being parsed.
+ * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static Script readScript(final XMLStreamReader reader, final Configuration configuration)
+ private static void readHistory(final XMLStreamReader reader, final Configuration configuration,
+ final SCXML scxml, final TransitionalState ts)
throws XMLStreamException, ModelException {
- final Script script = new Script();
- script.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
- if (script.getSrc() != null) {
- String resolvedSrc = script.getSrc();
- if (configuration.pathResolver != null) {
- resolvedSrc = configuration.pathResolver.resolvePath(resolvedSrc);
- }
- try (InputStream in = new URL(resolvedSrc).openStream()){
- script.setScript(IOUtils.toString(in, "UTF-8"));
- }
- catch (final IOException e) {
- throw new ModelException(e);
+ final History history = new History();
+ history.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_HISTORY));
+ history.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
+
+ ts.addHistory(history);
+ scxml.addTarget(history);
+
+ loop : while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
+ history.setTransition(readTransition(reader, configuration));
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_HISTORY, nsURI, name);
+ }
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ break loop;
+ default:
}
- skipToEndElement(reader);
- }
- else {
- script.setScript(readBody(reader));
}
- return script;
}
/**
- * Read the contents of this custom action.
+ * Read the contents of this <if> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param customAction The {@link CustomAction} to read.
- * @param executable The parent {@link Executable} for this custom action.
- * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one.
+ * @param executable The parent {@link Executable} for this action.
+ * @param parent The optional parent {@link ActionsContainer} if this <if> is a child of one.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
- final CustomAction customAction, final Executable executable,
- final ActionsContainer parent)
+ private static void readIf(final XMLStreamReader reader, final Configuration configuration,
+ final Executable executable, final ActionsContainer parent)
throws XMLStreamException, ModelException {
- // Instantiate custom action
- Object actionObject;
- final String className = customAction.getActionClass().getName();
- ClassLoader cl = configuration.customActionClassLoader;
- if (configuration.useContextClassLoaderForCustomActions) {
- cl = Thread.currentThread().getContextClassLoader();
- }
- if (cl == null) {
- cl = SCXMLReader.class.getClassLoader();
- }
- Class<?> clazz;
- try {
- clazz = cl.loadClass(className);
- actionObject = clazz.getConstructor().newInstance();
- } catch (final ClassNotFoundException cnfe) {
- throw new XMLStreamException("Cannot find custom action class:" + className, cnfe);
- } catch (final IllegalAccessException iae) {
- throw new XMLStreamException("Cannot access custom action class:" + className, iae);
- } catch (final ReflectiveOperationException ie) {
- throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie);
- }
- if (!(actionObject instanceof Action)) {
- throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className);
- }
-
- // Set the attribute values as properties
- final Action action = (Action) actionObject;
-
- final CustomActionWrapper actionWrapper = new CustomActionWrapper();
- actionWrapper.setAction(action);
- actionWrapper.setPrefix(reader.getPrefix());
- actionWrapper.setLocalName(reader.getLocalName());
- final Map<String, String> namespaces = readNamespaces(reader);
- if (namespaces != null) {
- actionWrapper.getNamespaces().putAll(namespaces);
+ final If iff = new If();
+ iff.setCond(readRequiredAV(reader, SCXMLConstants.ELEM_IF, SCXMLConstants.ATTR_COND));
+ iff.setParent(executable);
+ if (parent != null) {
+ parent.addAction(iff);
+ } else {
+ executable.addAction(iff);
}
+ readExecutableContext(reader, configuration, executable, iff);
+ }
- final Map<String, String> attributes = new HashMap<>();
- for (int i = 0; i < reader.getAttributeCount(); i++) {
- final String name = reader.getAttributeLocalName(i);
- final String qname = createQualifiedName(reader.getAttributePrefix(i), name);
- final String value = reader.getAttributeValue(i);
- attributes.put(qname, value);
- final String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
- Method method;
- try {
- method = clazz.getMethod(setter, String.class);
- method.invoke(action, value);
- } catch (final NoSuchMethodException nsme) {
- logger.warn("No method: " + setter + "(String) found in custom action class: " + className+ " for "
- + qname + "=\"" + value + "\". Attribute ignored");
- } catch (final InvocationTargetException ite) {
- throw new XMLStreamException("Exception calling method:" + setter + "(String) in custom action class:"
- + className, ite);
- } catch (final IllegalAccessException iae) {
- throw new XMLStreamException("Cannot access method: " + setter +"(String) in custom action class: "
- + className, iae);
+ /**
+ * Add all the nested targets from given target to given parent state machine.
+ *
+ * @param parent The state machine
+ * @param es The target to import
+ */
+ private static void readInExternalTargets(final SCXML parent, final EnterableState es) {
+ if (es instanceof TransitionalState) {
+ for (final History h : ((TransitionalState)es).getHistory()) {
+ parent.addTarget(h);
+ }
+ for (final EnterableState child : ((TransitionalState) es).getChildren()) {
+ parent.addTarget(child);
+ readInExternalTargets(parent, child);
}
- }
- if (!attributes.isEmpty()) {
- actionWrapper.setAttributes(attributes);
- }
-
- // Add any body content if necessary
- if (action instanceof ParsedValueContainer) {
- readParsedValue(reader, configuration, (ParsedValueContainer)action, false);
- }
- else {
- skipToEndElement(reader);
- }
-
- // Wire in the action and add to parent
- actionWrapper.setParent(executable);
- if (parent != null) {
- parent.addAction(actionWrapper);
- } else {
- executable.addAction(actionWrapper);
}
}
/**
- * Read and parse the body of a {@link ParsedValueContainer} element.
+ * Read the contents of this <initial> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param valueContainer The {@link ParsedValueContainer} element which body to read
- * @param invokeContent flag indicating if the valueContainer is a <invoke><content> element, which get special
- * treatment
+ * @param state The parent composite {@link State} for this initial.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static void readParsedValue(final XMLStreamReader reader, final Configuration configuration,
- final ParsedValueContainer valueContainer, final boolean invokeContent)
+ private static void readInitial(final XMLStreamReader reader, final Configuration configuration,
+ final State state)
throws XMLStreamException, ModelException {
- final Element element = readElement(reader);
- if (element.hasChildNodes()) {
- final NodeList children = element.getChildNodes();
- Node child = children.item(0);
- final boolean cdata = child.getNodeType() == Node.CDATA_SECTION_NODE;
- if (invokeContent) {
- // search or and only use first <scxml> element
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- for (int i = 1, size = children.getLength(); i < size; i++) {
- child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- break;
- }
- }
- }
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- if (SCXMLConstants.ELEM_SCXML.equals(child.getLocalName()) &&
- SCXMLConstants.XMLNS_SCXML.equals(child.getNamespaceURI())) {
- // transform <invoke><content><scxml> back to text
- try {
- valueContainer.setParsedValue(new NodeTextValue(ContentParser.DEFAULT_PARSER.toXml(child)));
- } catch (final IOException e) {
- throw new XMLStreamException(e);
+
+ final Initial initial = new Initial();
+
+ loop : while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
+ initial.setTransition(readSimpleTransition(reader, configuration));
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
}
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INITIAL, nsURI, name);
}
- }
- if (valueContainer.getParsedValue() == null) {
- reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.XMLNS_SCXML,
- SCXMLConstants.ELEM_CONTENT);
- }
- }
- else if (children.getLength() == 1 && (cdata || child.getNodeType() == Node.TEXT_NODE )) {
- final String text = ContentParser.trimContent(child.getNodeValue());
- if (ContentParser.hasJsonSignature(text)) {
- try {
- valueContainer.setParsedValue(new JsonValue(configuration.contentParser.parseJson(text), cdata));
- } catch (final IOException e) {
- throw new ModelException(e);
- }
- }
- else {
- valueContainer.setParsedValue(new TextValue(ContentParser.spaceNormalizeContent(text),
- cdata));
- }
- } else if (children.getLength() == 1) {
- valueContainer.setParsedValue(new NodeValue(child));
- } else {
- final ArrayList<Node> nodeList = new ArrayList<>();
- for (int i = 0, size = children.getLength(); i < size; i++) {
- nodeList.add(children.item(i));
- }
- valueContainer.setParsedValue(new NodeListValue(nodeList));
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ break loop;
+ default:
}
}
+
+ state.setInitial(initial);
}
+ //---------------------- PRIVATE UTILITY METHODS ----------------------//
/**
- * Read the current element into a DOM {@link Element}.
+ * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not
+ * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source
+ * parameters must be provided.
*
- * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use when parsing the SCXML document.
+ * @param scxmlURL The optional SCXML document {@link URL} to parse.
+ * @param scxmlPath The optional real path to the SCXML document as a string.
+ * @param scxmlStream The optional {@link InputStream} providing the SCXML document.
+ * @param scxmlReader The optional {@link Reader} providing the SCXML document.
+ * @param scxmlSource The optional {@link Source} providing the SCXML document.
*
- * @return The parsed content as a DOM {@link Element}.
+ * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
+ * (not wired up to be immediately usable).
*
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static Element readElement(final XMLStreamReader reader)
- throws XMLStreamException {
+ private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath,
+ final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource)
+ throws IOException, ModelException, XMLStreamException {
- // Create a document in which to build the DOM node
- Document document;
- try {
- document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
- } catch (final ParserConfigurationException pce) {
- throw new XMLStreamException(ERR_PARSER_CFG);
+ if (configuration.pathResolver == null) {
+ if (scxmlURL != null) {
+ configuration.pathResolver = new URLResolver(scxmlURL);
+ } else if (scxmlPath != null) {
+ configuration.pathResolver = new URLResolver(new URL(scxmlPath));
+ }
}
- // This root element will be returned, add any attributes as specified
- final Element root = document.createElementNS(reader.getNamespaceURI(), createQualifiedName(reader.getPrefix(), reader.getLocalName()));
- document.appendChild(root);
-
- boolean children = false, cdata = false;
- Node parent = root;
+ final XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource);
- // Convert stream to DOM node(s) while maintaining parent child relationships
- loop : while (reader.hasNext()) {
- Node child = null;
- switch (reader.next()) {
- case XMLStreamConstants.START_ELEMENT:
- if (!children && root.hasChildNodes()) {
- // remove any children
- root.setTextContent(null);
- }
- children = true;
- final Element elem = document.createElementNS(reader.getNamespaceURI(),
- createQualifiedName(reader.getPrefix(), reader.getLocalName()));
- for (int i = 0; i < reader.getAttributeCount(); i++) {
- final Attr attr = document.createAttributeNS(reader.getAttributeNamespace(i),
- createQualifiedName(reader.getAttributePrefix(i), reader.getAttributeLocalName(i)));
- attr.setValue(reader.getAttributeValue(i));
- elem.setAttributeNodeNS(attr);
- }
- parent.appendChild(elem);
- parent = elem;
- break;
- case XMLStreamConstants.SPACE:
- case XMLStreamConstants.CHARACTERS:
- case XMLStreamConstants.ENTITY_REFERENCE:
- if (!children || parent != root) {
- child = document.createTextNode(reader.getText());
- }
- break;
- case XMLStreamConstants.CDATA:
- if (!children || parent != root) {
- cdata = true;
- child = document.createCDATASection(reader.getText());
- }
- break;
- case XMLStreamConstants.END_ELEMENT:
- parent = parent.getParentNode();
- if (parent == document) {
- break loop;
- }
- break;
- default: // rest is ignored
- }
- if (child != null) {
- parent.appendChild(child);
- }
- }
- if (!children && root.hasChildNodes() && root.getChildNodes().getLength() > 1) {
- final String text = root.getTextContent().trim();
- if (!cdata) {
- root.setTextContent(text);
- } else {
- root.setTextContent(null);
- root.appendChild(document.createCDATASection(text));
- }
- }
- return root;
+ return readDocument(reader, configuration);
}
/**
- * Read the following body contents into a String.
+ * Read the contents of this <invoke> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- *
- * @return The body content read into a String.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param parent The parent {@link TransitionalState} for this invoke.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static String readBody(final XMLStreamReader reader)
- throws XMLStreamException {
+ private static void readInvoke(final XMLStreamReader reader, final Configuration configuration,
+ final TransitionalState parent)
+ throws XMLStreamException, ModelException {
- final StringBuilder body = new StringBuilder();
+ final Invoke invoke = new Invoke();
+ invoke.setId(readAV(reader, SCXMLConstants.ATTR_ID));
+ invoke.setIdlocation(readAV(reader, SCXMLConstants.ATTR_IDLOCATION));
+ invoke.setSrc(readAV(reader, SCXMLConstants.ATTR_SRC));
+ invoke.setSrcexpr(readAV(reader, SCXMLConstants.ATTR_SRCEXPR));
+ invoke.setType(readAV(reader, SCXMLConstants.ATTR_TYPE));
+ invoke.setAutoForward(readBooleanAV(reader, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.ATTR_AUTOFORWARD));
+ invoke.setNamelist(readAV(reader, SCXMLConstants.ATTR_NAMELIST));
- // Add all body content to StringBuilder
loop : while (reader.hasNext()) {
+ String name, nsURI;
switch (reader.next()) {
case XMLStreamConstants.START_ELEMENT:
- logger.warn("Ignoring XML content in <script> element, encountered element: "
- + createQualifiedName(reader.getPrefix(), reader.getLocalName()));
- skipToEndElement(reader);
- break;
- case XMLStreamConstants.SPACE:
- case XMLStreamConstants.CHARACTERS:
- case XMLStreamConstants.ENTITY_REFERENCE:
- case XMLStreamConstants.CDATA:
- body.append(reader.getText());
- break;
- case XMLStreamConstants.COMMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_PARAM.equals(name)) {
+ readParam(reader, configuration, invoke);
+ } else if (SCXMLConstants.ELEM_FINALIZE.equals(name)) {
+ readFinalize(reader, configuration, parent, invoke);
+ } else if (SCXMLConstants.ELEM_CONTENT.equals(name)) {
+ readContent(reader, configuration, invoke);
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, nsURI, name);
+ }
break;
case XMLStreamConstants.END_ELEMENT:
break loop;
- default: // rest is ignored
+ default:
}
}
- return body.toString();
- }
- /**
- * @param prefix prefix
- * @param localName localName
- * @return a qualified name from a prefix and localName
- */
- private static String createQualifiedName(final String prefix, final String localName) {
- return (prefix != null && !prefix.isEmpty() ? prefix + ":" : "") + localName;
+ parent.addInvoke(invoke);
}
/**
- * @param input input string to check if null or empty after trim
- * @return null if input is null or empty after trim()
+ * Read the contents of this <log> element.
+ *
+ * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+ * @param executable The parent {@link Executable} for this action.
+ * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
+ *
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static String nullIfEmpty(final String input) {
- return input == null || input.trim().length()==0 ? null : input.trim();
+ private static void readLog(final XMLStreamReader reader, final Executable executable, final ActionsContainer parent)
+ throws XMLStreamException {
+
+ final Log log = new Log();
+ log.setExpr(readAV(reader, SCXMLConstants.ATTR_EXPR));
+ log.setLabel(readAV(reader, SCXMLConstants.ATTR_LABEL));
+ log.setParent(executable);
+ if (parent != null) {
+ parent.addAction(log);
+ } else {
+ executable.addAction(log);
+ }
+ skipToEndElement(reader);
}
/**
- * Gets the attribute value at the current reader location.
+ * Read the current active namespace declarations.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param attrLocalName The attribute name whose value is needed.
- *
- * @return The value of the attribute.
+ * @return the map of active namespace declarations, null if none defined
*/
- private static String readAV(final XMLStreamReader reader, final String attrLocalName) {
- return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
+ private static Map<String, String> readNamespaces(final XMLStreamReader reader) {
+ Map<String, String> namespaces = null;
+ if (reader.getNamespaceCount() > 0) {
+ namespaces = new LinkedHashMap<>();
+ for (int i = 0; i < reader.getNamespaceCount(); i++) {
+ namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
+ }
+ }
+ return namespaces;
}
/**
- * Gets the Boolean attribute value at the current reader location.
+ * Read the contents of this <onentry> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param elementName The name of the element for which the attribute value is needed.
- * @param attrLocalName The attribute name whose value is needed.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param es The parent {@link EnterableState} for this onentry.
*
- * @return The Boolean value of the attribute.
- * @throws ModelException When the attribute value is not empty but neither "true" or "false".
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName,
- final String attrLocalName)
- throws ModelException {
- final String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
- final Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
- if (result == null && value != null) {
- final MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN);
- final String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()});
- throw new ModelException(errMsg);
- }
- return result;
+ private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration,
+ final EnterableState es)
+ throws XMLStreamException, ModelException {
+
+ final OnEntry onentry = new OnEntry();
+ onentry.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONENTRY, SCXMLConstants.ATTR_EVENT));
+ readExecutableContext(reader, configuration, onentry, null);
+ es.addOnEntry(onentry);
}
/**
- * Gets a required attribute value at the current reader location,
+ * Read the contents of this <onexit> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @param elementName The name of the element for which the attribute value is needed.
- * @param attrLocalName The attribute name whose value is needed.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param es The parent {@link EnterableState} for this onexit.
*
- * @return The value of the attribute.
- * @throws ModelException When the required attribute is missing or empty.
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
*/
- private static String readRequiredAV(final XMLStreamReader reader, final String elementName, final String attrLocalName)
- throws ModelException {
- final String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
- if (value == null) {
- reportMissingAttribute(reader.getLocation(), elementName, attrLocalName);
- }
- return value;
+ private static void readOnExit(final XMLStreamReader reader, final Configuration configuration,
+ final EnterableState es)
+ throws XMLStreamException, ModelException {
+
+ final OnExit onexit = new OnExit();
+ onexit.setRaiseEvent(readBooleanAV(reader, SCXMLConstants.ELEM_ONEXIT, SCXMLConstants.ATTR_EVENT));
+ readExecutableContext(reader, configuration, onexit, null);
+ es.addOnExit(onexit);
}
private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml,
@@ -2335,606 +2051,890 @@ public final class SCXMLReader {
}
/**
- * Read the current active namespace declarations.
+ * Read the contents of this <parallel> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
- * @return the map of active namespace declarations, null if none defined
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param scxml The root of the object model being parsed.
+ * @param parent The parent {@link TransitionalState} for this parallel (null for top level state).
+ *
+ * @throws IOException An IO error during parsing.
+ * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
+ * errors in the SCXML document that may not be identified by the schema).
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static Map<String, String> readNamespaces(final XMLStreamReader reader) {
- Map<String, String> namespaces = null;
- if (reader.getNamespaceCount() > 0) {
- namespaces = new LinkedHashMap<>();
- for (int i = 0; i < reader.getNamespaceCount(); i++) {
- namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
- }
- }
- return namespaces;
- }
+ private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
+ final TransitionalState parent)
+ throws IOException, ModelException, XMLStreamException {
- /**
- * Report a missing required attribute value at the current reader location,
- *
- * @param location The (approximate) {@link Location} where the attribute is expected.
- * @param elementName The name of the element for which the attribute value is needed.
- * @param attrLocalName The attribute name whose value is needed.
- *
- * @throws ModelException The required attribute is missing or empty.
- */
- private static void reportMissingAttribute(final Location location, final String elementName, final String attrLocalName)
- throws ModelException {
- final MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING);
- final String errMsg = msgFormat.format(new Object[] {elementName, attrLocalName, location});
- throw new ModelException(errMsg);
+ final Parallel parallel = new Parallel();
+ parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, SCXMLConstants.ELEM_PARALLEL));
+ final String src = readAV(reader, SCXMLConstants.ATTR_SRC);
+ if (src != null) {
+ String source = src;
+ final Configuration copy = new Configuration(configuration);
+ if (copy.parent == null) {
+ copy.parent = scxml;
+ }
+ if (configuration.pathResolver != null) {
+ source = configuration.pathResolver.resolvePath(src);
+ copy.pathResolver = configuration.pathResolver.getResolver(src);
+ }
+ readTransitionalStateSrc(copy, source, parallel);
+ }
+
+ if (parent == null) {
+ scxml.addChild(parallel);
+ } else if (parent instanceof State) {
+ ((State)parent).addChild(parallel);
+ }
+ else {
+ ((Parallel)parent).addChild(parallel);
+ }
+ scxml.addTarget(parallel);
+ if (configuration.parent != null) {
+ configuration.parent.addTarget(parallel);
+ }
+
+ loop : while (reader.hasNext()) {
+ String name, nsURI;
+ switch (reader.next()) {
+ case XMLStreamConstants.START_ELEMENT:
+ nsURI = reader.getNamespaceURI();
+ name = reader.getLocalName();
+ if (SCXMLConstants.XMLNS_SCXML.equals(nsURI)) {
+ if (SCXMLConstants.ELEM_TRANSITION.equals(name)) {
+ parallel.addTransition(readTransition(reader, configuration));
+ } else if (SCXMLConstants.ELEM_STATE.equals(name)) {
+ readState(reader, configuration, scxml, parallel);
+ } else if (SCXMLConstants.ELEM_PARALLEL.equals(name)) {
+ readParallel(reader, configuration, scxml, parallel);
+ } else if (SCXMLConstants.ELEM_ONENTRY.equals(name)) {
+ readOnEntry(reader, configuration, parallel);
+ } else if (SCXMLConstants.ELEM_ONEXIT.equals(name)) {
+ readOnExit(reader, configuration, parallel);
+ } else if (SCXMLConstants.ELEM_DATAMODEL.equals(name)) {
+ readDatamodel(reader, configuration, null, parallel);
+ } else if (SCXMLConstants.ELEM_INVOKE.equals(name)) {
+ readInvoke(reader, configuration, parallel);
+ } else if (SCXMLConstants.ELEM_HISTORY.equals(name)) {
+ readHistory(reader, configuration, scxml, parallel);
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
+ }
+ } else {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_PARALLEL, nsURI, name);
+ }
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ break loop;
+ default:
+ }
+ }
}
/**
- * Report an ignored element via the {@link XMLReporter} if available and the class
- * {@link org.apache.commons.logging.Log}.
+ * Read the contents of this <param> element.
*
* @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
* @param configuration The {@link Configuration} to use while parsing.
- * @param parent The parent element local name in the SCXML namespace.
- * @param nsURI The namespace URI of the ignored element.
- * @param name The local name of the ignored element.
+ * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param.
*
* @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
- * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
- * errors in the SCXML document that may not be identified by the schema).
*/
- private static void reportIgnoredElement(final XMLStreamReader reader, final Configuration configuration,
- final String parent, final String nsURI, final String name)
+ private static void readParam(final XMLStreamReader reader, final Configuration configuration,
+ final ParamsContainer parent)
throws XMLStreamException, ModelException {
- final StringBuilder sb = new StringBuilder();
- sb.append("Ignoring unknown or invalid element <").append(name)
- .append("> in namespace \"").append(nsURI)
- .append("\" as child of <").append(parent)
- .append("> at ").append(reader.getLocation());
- if (!configuration.isSilent() && logger.isWarnEnabled()) {
- logger.warn(sb.toString());
+ final Param param = new Param();
+ param.setName(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_NAME));
+ final String location = readAV(reader, SCXMLConstants.ATTR_LOCATION);
+ final String expr = readAV(reader, SCXMLConstants.ATTR_EXPR);
+ if (expr != null) {
+ if (location != null) {
+ reportConflictingAttribute(reader, configuration, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION, SCXMLConstants.ATTR_EXPR);
+ }
+ else {
+ param.setExpr(expr);
+ }
}
- if (configuration.isStrict()) {
- throw new ModelException(sb.toString());
+ else if (location == null) {
+ // force error missing required location or expr: use location attr for this
+ param.setLocation(readRequiredAV(reader, SCXMLConstants.ELEM_PARAM, SCXMLConstants.ATTR_LOCATION));
}
- final XMLReporter reporter = configuration.reporter;
- if (reporter != null) {
- reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
+ else {
+ param.setLocation(location);
}
+ parent.getParams().add(param);
skipToEndElement(reader);
}
/**
- * Advances the XMLStreamReader until after the end of the current element: all children will be skipped as well
- * @param reader the reader
- * @throws XMLStreamException
+ * Read and parse the body of a {@link ParsedValueContainer} element.
+ *
+ * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
+ * @param configuration The {@link Configuration} to use while parsing.
+ * @param valueContainer The {@link ParsedValueContainer} element which body to read
+ * @param invokeContent flag indicating if the valueContainer is a <invoke><content> element, which get special
+ * treatment
+ *
+ * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
*/
- private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException {
- int elementsToSkip = 1;
- while (elementsToSkip > 0 && reader.hasNext()) {
- final int next = reader.next();
- if (next == XMLStreamConstants.START_ELEMENT) {
- elementsToSkip++;
+ private static void readParsedValue(final XMLStreamReader reader, final Configuration configuration,
+ final ParsedValueContainer valueContainer, final boolean invokeContent)
+ throws XMLStreamException, ModelException {
+ final Element element = readElement(reader);
+ if (element.hasChildNodes()) {
+ final NodeList children = element.getChildNodes();
+ Node child = children.item(0);
+ final boolean cdata = child.getNodeType() == Node.CDATA_SECTION_NODE;
+ if (invokeContent) {
+ // search or and only use first <scxml> element
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ for (int i = 1, size = children.getLength(); i < size; i++) {
+ child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ break;
+ }
+ }
+ }
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ if (SCXMLConstants.ELEM_SCXML.equals(child.getLocalName()) &&
+ SCXMLConstants.XMLNS_SCXML.equals(child.getNamespaceURI())) {
+ // transform <invoke><content><scxml> back to text
+ try {
+ valueContainer.setParsedValue(new NodeTextValue(ContentParser.DEFAULT_PARSER.toXml(child)));
+ } catch (final IOException e) {
+ throw new XMLStreamException(e);
+ }
+ }
+ }
+ if (valueContainer.getParsedValue() == null) {
+ reportIgnoredElement(reader, configuration, SCXMLConstants.ELEM_INVOKE, SCXMLConstants.XMLNS_SCXML,
+ SCXMLConstants.ELEM_CONTENT);
+ }
}
- else if (next == XMLStreamConstants.END_ELEMENT) {
- elementsToSkip--;
+ else if (children.getLength() == 1 && (cdata || child.getNodeType() == Node.TEXT_NODE )) {
+ final String text = ContentParser.trimContent(child.getNodeValue());
+ if (ContentParser.hasJsonSignature(text)) {
+ try {
+ valueContainer.setParsedValue(new JsonValue(configuration.contentParser.parseJson(text), cdata));
+ } catch (final IOException e) {
+ throw new ModelException(e);
+ }
+ }
+ else {
+ valueContainer.setParsedValue(new TextValue(ContentParser.spaceNormalizeContent(text),
+ cdata));
+ }
+ } else if (children.getLength() == 1) {
... 8948 lines suppressed ...