org.apache.commons.scxml2.SCXMLExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.scxml2.SCXMLExecutor.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.scxml2;

import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.scxml2.invoke.Invoker;
import org.apache.commons.scxml2.model.EnterableState;
import org.apache.commons.scxml2.model.ModelException;
import org.apache.commons.scxml2.model.Observable;
import org.apache.commons.scxml2.model.SCXML;
import org.apache.commons.scxml2.model.TransitionTarget;
import org.apache.commons.scxml2.semantics.SCXMLSemanticsImpl;

/**
 * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
 * particular semantics used by this engine for executing the SCXML are
 * encapsulated in the SCXMLSemantics implementation that it uses.</p>
 *
 * <p>The default implementation is
 * <code>org.apache.commons.scxml2.semantics.SCXMLSemanticsImpl</code></p>
 *
 * <p>The executor uses SCXMLExecutionContext to manage the state and
 * provide all the services to the SCXMLSemantics implementation.</p>
 *
 * @see SCXMLSemantics
 */
public class SCXMLExecutor implements SCXMLIOProcessor {

    /**
     * The Logger for the SCXMLExecutor.
     */
    private Log log = LogFactory.getLog(SCXMLExecutor.class);

    /**
     * Parent SCXMLExecutor
     */
    private SCXMLExecutor parentSCXMLExecutor;

    /**
     *  Interpretation semantics.
     */
    private SCXMLSemantics semantics;

    /**
     * The state machine execution context
     */
    private SCXMLExecutionContext exctx;

    /**
     * The external event queue
     */
    private final Queue<TriggerEvent> externalEventQueue = new ConcurrentLinkedQueue<TriggerEvent>();

    /**
     * Convenience constructor.
     */
    public SCXMLExecutor() {
        this(null, null, null, null);
    }

    /**
     * Constructor.
     *
     * @param expEvaluator The expression evaluator
     * @param evtDisp The event dispatcher
     * @param errRep The error reporter
     */
    public SCXMLExecutor(final Evaluator expEvaluator, final EventDispatcher evtDisp, final ErrorReporter errRep) {
        this(expEvaluator, evtDisp, errRep, null);
    }

    /**
     * Constructor.
     *
     * @param expEvaluator The expression evaluator
     * @param evtDisp The event dispatcher
     * @param errRep The error reporter
     * @param semantics The SCXML semantics
     */
    public SCXMLExecutor(final Evaluator expEvaluator, final EventDispatcher evtDisp, final ErrorReporter errRep,
            final SCXMLSemantics semantics) {
        this.semantics = semantics != null ? semantics : new SCXMLSemanticsImpl();
        this.exctx = new SCXMLExecutionContext(this, expEvaluator, evtDisp, errRep);
    }

    /**
     * Constructor using a parent SCXMLExecutor
     *
     * @param parentSCXMLExecutor the parent SCXMLExecutor
     */
    public SCXMLExecutor(final SCXMLExecutor parentSCXMLExecutor) {
        this.parentSCXMLExecutor = parentSCXMLExecutor;
        this.semantics = parentSCXMLExecutor.semantics;
        this.exctx = new SCXMLExecutionContext(this, parentSCXMLExecutor.getEvaluator(),
                parentSCXMLExecutor.getEventdispatcher(), parentSCXMLExecutor.getErrorReporter());
    }

    /**
     * @return the parent SCXMLExecutor (if any)
     */
    protected SCXMLExecutor getParentSCXMLExecutor() {
        return parentSCXMLExecutor;
    }

    /**
     * Get the current state machine instance status.
     *
     * @return The current Status
     */
    public synchronized Status getStatus() {
        return exctx.getScInstance().getCurrentStatus();
    }

    /**
     * Initializes the state machine with a specific active configuration
     * <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.
     * </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)
     */
    public synchronized void setConfiguration(Set<String> atomicStateIds) throws ModelException {
        exctx.initialize();
        Set<EnterableState> states = new HashSet<EnterableState>();
        for (String stateId : atomicStateIds) {
            TransitionTarget tt = getStateMachine().getTargets().get(stateId);
            if (tt instanceof EnterableState && ((EnterableState) tt).isAtomicState()) {
                EnterableState es = (EnterableState) tt;
                while (es != null && !states.add(es)) {
                    es = es.getParent();
                }
            } else {
                throw new ModelException(
                        "Illegal atomic stateId " + stateId + ": state unknown or not an atomic state");
            }
        }
        if (semantics.isLegalConfiguration(states, getErrorReporter())) {
            for (EnterableState es : states) {
                exctx.getScInstance().getStateConfiguration().enterState(es);
            }
            logState();
        } else {
            throw new ModelException("Illegal state machine configuration!");
        }
    }

    /**
     * Set 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 void setEvaluator(final Evaluator evaluator) throws ModelException {
        exctx.setEvaluator(evaluator);
    }

    /**
     * Get the expression evaluator in use.
     *
     * @return Evaluator The evaluator in use.
     */
    public Evaluator getEvaluator() {
        return exctx.getEvaluator();
    }

    /**
     * Get 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();
    }

    /**
     * Get the global context for the state machine execution.
     * <p>
     * The global context is the top level context within the state machine itself and should be regarded and treated
     * "read-only" from external usage.
     * </p>
     * @return Context The global context.
     */
    public Context getGlobalContext() {
        return exctx.getScInstance().getGlobalContext();
    }

    /**
     * Set 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(boolean singleContext) throws ModelException {
        getSCInstance().setSingleContext(singleContext);
    }

    public boolean isSingleContext() {
        return getSCInstance().isSingleContext();
    }

    /**
     * Get the state machine that is being executed.
     * <b>NOTE:</b> This is the state machine definition or model used by this
     * executor instance. It may be shared across multiple executor instances
     * and should not be altered once in use. Also note that
     * manipulation of instance data for the executor should happen through
     * its root context or state contexts only, never through the direct
     * manipulation of any {@link org.apache.commons.scxml2.model.Datamodel}s associated with this state
     * machine definition.
     *
     * @return Returns the stateMachine.
     */
    public SCXML getStateMachine() {
        return exctx.getStateMachine();
    }

    /**
     * Set 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();
    }

    /**
     * Get the environment specific error reporter.
     *
     * @return Returns the errorReporter.
     */
    public ErrorReporter getErrorReporter() {
        return exctx.getErrorReporter();
    }

    /**
     * Set 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);
    }

    /**
     * Get the event dispatcher.
     *
     * @return Returns the eventdispatcher.
     */
    public EventDispatcher getEventdispatcher() {
        return exctx.getEventDispatcher();
    }

    /**
     * Set 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);
    }

    /**
     * Set if the SCXML configuration should be checked before execution (default = true)
     * @param checkLegalConfiguration flag to set
     */
    public void setCheckLegalConfiguration(boolean checkLegalConfiguration) {
        this.exctx.setCheckLegalConfiguration(checkLegalConfiguration);
    }

    /**
     * @return if the SCXML configuration will be checked before execution
     */
    public boolean isCheckLegalConfiguration() {
        return exctx.isCheckLegalConfiguration();
    }

    /**
     * Get the notification registry.
     *
     * @return The notification registry.
     */
    public NotificationRegistry getNotificationRegistry() {
        return exctx.getNotificationRegistry();
    }

    /**
     * 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);
    }

    /**
     * Remove this listener from the {@link Observable}.
     *
     * @param observable The {@link Observable}.
     * @param listener The SCXMLListener to be removed.
     */
    public void removeListener(final Observable observable, final SCXMLListener listener) {
        exctx.getNotificationRegistry().removeListener(observable, listener);
    }

    /**
     * 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.
     */
    public void registerInvokerClass(final String type, final Class<? extends Invoker> invokerClass) {
        exctx.registerInvokerClass(type, invokerClass);
    }

    /**
     * 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 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(SCInstance instance) {
        exctx.attachInstance(instance);
    }

    /**
     * @return Returns true if the state machine is running
     */
    public boolean isRunning() {
        return exctx.isRunning();
    }

    /**
     * Initiate state machine execution.
     *
     * @throws ModelException in case there is a fatal SCXML object
     *  model problem.
     */
    public void go() throws ModelException {
        // same as reset
        this.reset();
    }

    /**
     * Clear all state and begin executing the state machine
     *
     * @throws ModelException if the state machine instance failed to initialize
     */
    public void reset() throws ModelException {
        // clear any pending external events
        externalEventQueue.clear();

        // go
        semantics.firstStep(exctx);
        logState();
    }

    /**
     * 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 void addEvent(final TriggerEvent evt) {
        if (evt != null) {
            externalEventQueue.add(evt);
        }
    }

    /**
     * @return Returns true if there are pending external events to be processed.
     */
    public boolean hasPendingEvents() {
        return !externalEventQueue.isEmpty();
    }

    /**
     * @return Returns the current number of pending external events to be processed.
     */
    public int getPendingEvents() {
        return externalEventQueue.size();
    }

    /**
     * Convenience method when only one event needs to be triggered.
     *
     * @param evt
     *            the external events which triggered during the last
     *            time quantum
     * @throws ModelException in case there is a fatal SCXML object
     *            model problem.
     */
    public void triggerEvent(final TriggerEvent evt) throws ModelException {
        addEvent(evt);
        triggerEvents();
    }

    /**
     * The worker method.
     * Re-evaluates current status whenever any events are triggered.
     *
     * @param evts
     *            an array of external events which triggered during the last
     *            time quantum
     * @throws ModelException in case there is a fatal SCXML object
     *            model problem.
     */
    public void triggerEvents(final TriggerEvent[] evts) throws ModelException {
        if (evts != null) {
            for (TriggerEvent evt : evts) {
                addEvent(evt);
            }
        }
        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);
        }
    }

    protected void eventStep(TriggerEvent event) throws ModelException {
        semantics.nextStep(exctx, event);
        logState();
    }

    /**
     * Get the state chart instance for this executor.
     *
     * @return The SCInstance for this executor.
     */
    protected SCInstance getSCInstance() {
        return exctx.getScInstance();
    }

    /**
     * Log the current set of active states.
     */
    protected void logState() {
        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder("Current States: [ ");
            for (EnterableState es : getStatus().getStates()) {
                sb.append(es.getId()).append(", ");
            }
            int length = sb.length();
            sb.delete(length - 2, length).append(" ]");
            log.debug(sb.toString());
        }
    }
}