001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.model;
018
019import java.io.Serializable;
020import java.util.Map;
021
022import org.apache.commons.scxml2.ActionExecutionContext;
023import org.apache.commons.scxml2.Context;
024import org.apache.commons.scxml2.SCXMLExpressionException;
025import org.w3c.dom.Node;
026
027/**
028 * An abstract base class for executable elements in SCXML,
029 * such as <assign>, <log> etc.
030 *
031 */
032public abstract class Action implements NamespacePrefixesHolder,
033        Serializable {
034
035    /**
036     * Link to its parent or container.
037     */
038    private Executable parent;
039
040    /**
041     * The current XML namespaces in the SCXML document for this action node,
042     * preserved for deferred XPath evaluation.
043     */
044    private Map<String, String> namespaces;
045
046    /**
047     * Constructor.
048     */
049    public Action() {
050        super();
051        this.parent = null;
052        this.namespaces = null;
053    }
054
055    /**
056     * Get the Executable parent.
057     *
058     * @return Returns the parent.
059     */
060    public final Executable getParent() {
061        return parent;
062    }
063
064    /**
065     * Set the Executable parent.
066     *
067     * @param parent The parent to set.
068     */
069    public final void setParent(final Executable parent) {
070        this.parent = parent;
071    }
072
073    /**
074     * Get the XML namespaces at this action node in the SCXML document.
075     *
076     * @return Returns the map of namespaces.
077     */
078    public final Map<String, String> getNamespaces() {
079        return namespaces;
080    }
081
082    /**
083     * Set the XML namespaces at this action node in the SCXML document.
084     *
085     * @param namespaces The document namespaces.
086     */
087    public final void setNamespaces(final Map<String, String> namespaces) {
088        this.namespaces = namespaces;
089    }
090
091    /**
092     * Return the {@link EnterableState} whose {@link org.apache.commons.scxml2.Context} this action
093     * executes in.
094     *
095     * @return The parent {@link EnterableState}
096     * @throws ModelException For an unknown EnterableState subclass
097     *
098     * @since 0.9
099     */
100    public EnterableState getParentEnterableState()
101    throws ModelException {
102        if (parent == null && this instanceof Script && ((Script)this).isGlobalScript()) {
103            // global script doesn't have a EnterableState
104            return null;
105        }
106        else if (parent == null) {
107            throw new ModelException("Action "
108                    + this.getClass().getName() + " instance missing required parent TransitionTarget");
109        }
110        TransitionTarget tt = parent.getParent();
111        if (tt instanceof EnterableState) {
112            return (EnterableState)tt;
113        } else if (tt instanceof History) {
114            return ((History)tt).getParent();
115        } else {
116            throw new ModelException("Unknown TransitionTarget subclass:"
117                    + (tt != null ? tt.getClass().getName() : "(null)"));
118        }
119    }
120
121    /**
122     * Execute this action instance.
123     *
124     * @param exctx The ActionExecutionContext for this execution instance
125     *
126     * @throws ModelException If the execution causes the model to enter
127     *                        a non-deterministic state.
128     * @throws SCXMLExpressionException If the execution involves trying
129     *                        to evaluate an expression which is malformed.
130     */
131    public abstract void execute(ActionExecutionContext exctx) throws ModelException, SCXMLExpressionException;
132
133    /**
134     * Return the key under which the current document namespaces are saved
135     * in the parent state's context.
136     *
137     * @return The namespaces key
138     */
139    protected static String getNamespacesKey() {
140        return Context.NAMESPACES_KEY;
141    }
142
143    /**
144     * Convenient method to convert a possible {@link Node} result from an expression evaluation to a String
145     * using its {@link Node#getTextContent()} method.
146     * @param result the result to convert
147     * @return its text content if the result is a {@link Node} otherwise the unmodified result itself
148     */
149    protected Object getTextContentIfNodeResult(final Object result) {
150        if (result instanceof Node) {
151            return ((Node)result).getTextContent();
152        }
153        return result;
154    }
155}
156