org.apache.commons.scxml2.io.SCXMLReader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.scxml2.io.SCXMLReader.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.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLReporter;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.XMLEventAllocator;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.apache.commons.logging.LogFactory;
import org.apache.commons.scxml2.Evaluator;
import org.apache.commons.scxml2.PathResolver;
import org.apache.commons.scxml2.env.SimpleErrorHandler;
import org.apache.commons.scxml2.env.URLResolver;
import org.apache.commons.scxml2.model.Action;
import org.apache.commons.scxml2.model.ActionsContainer;
import org.apache.commons.scxml2.model.Assign;
import org.apache.commons.scxml2.model.Cancel;
import org.apache.commons.scxml2.model.Content;
import org.apache.commons.scxml2.model.ContentContainer;
import org.apache.commons.scxml2.model.CustomAction;
import org.apache.commons.scxml2.model.Data;
import org.apache.commons.scxml2.model.Datamodel;
import org.apache.commons.scxml2.model.Else;
import org.apache.commons.scxml2.model.ElseIf;
import org.apache.commons.scxml2.model.EnterableState;
import org.apache.commons.scxml2.model.Executable;
import org.apache.commons.scxml2.model.ExternalContent;
import org.apache.commons.scxml2.model.Final;
import org.apache.commons.scxml2.model.Finalize;
import org.apache.commons.scxml2.model.Foreach;
import org.apache.commons.scxml2.model.History;
import org.apache.commons.scxml2.model.If;
import org.apache.commons.scxml2.model.Initial;
import org.apache.commons.scxml2.model.Invoke;
import org.apache.commons.scxml2.model.Log;
import org.apache.commons.scxml2.model.ModelException;
import org.apache.commons.scxml2.model.NamespacePrefixesHolder;
import org.apache.commons.scxml2.model.OnEntry;
import org.apache.commons.scxml2.model.OnExit;
import org.apache.commons.scxml2.model.Parallel;
import org.apache.commons.scxml2.model.Param;
import org.apache.commons.scxml2.model.ParamsContainer;
import org.apache.commons.scxml2.model.Raise;
import org.apache.commons.scxml2.model.SCXML;
import org.apache.commons.scxml2.model.Script;
import org.apache.commons.scxml2.model.Send;
import org.apache.commons.scxml2.model.SimpleTransition;
import org.apache.commons.scxml2.model.State;
import org.apache.commons.scxml2.model.Transition;
import org.apache.commons.scxml2.model.TransitionType;
import org.apache.commons.scxml2.model.TransitionalState;
import org.apache.commons.scxml2.model.Var;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * <p>The SCXMLReader provides the ability to read a SCXML document into
 * the Java object model provided in the model package.</p>
 *
 * <p>See latest version of the SCXML Working Draft for more details.</p>
 *
 * <p><b>NOTE:</b> The SCXMLReader assumes that the SCXML document to be
 * parsed is well-formed and correct. If that assumption does not hold,
 * any subsequent behavior is undefined.</p>
 *
 * @since 1.0
 */
public final class SCXMLReader {

    //---------------------- PRIVATE CONSTANTS ----------------------//
    //---- NAMESPACES ----//
    /**
     * The SCXML namespace that this Reader is built for. Any document
     * that is intended to be parsed by this reader <b>must</b>
     * bind the SCXML elements to this namespace.
     */
    private static final String XMLNS_SCXML = "http://www.w3.org/2005/07/scxml";

    /**
     * The namespace that defines any custom actions defined by the Commons
     * SCXML implementation. Any document that intends to use these custom
     * actions needs to ensure that they are in the correct namespace. Use
     * of actions in this namespace makes the document non-portable across
     * implementations.
     */
    private static final String XMLNS_COMMONS_SCXML = "http://commons.apache.org/scxml";

    /**
     * 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 ----//
    /**
     * Null URL passed as argument.
     */
    private static final String ERR_NULL_URL = "Cannot parse null URL";

    /**
     * Null path passed as argument.
     */
    private static final String ERR_NULL_PATH = "Cannot parse null path";

    /**
     * Null InputStream passed as argument.
     */
    private static final String ERR_NULL_ISTR = "Cannot parse null InputStream";

    /**
     * Null Reader passed as argument.
     */
    private static final String ERR_NULL_READ = "Cannot parse null Reader";

    /**
     * Null Source passed as argument.
     */
    private static final String ERR_NULL_SRC = "Cannot parse null Source";

    /**
     * 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: ";

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

    /**
     * Error message when the URI in a &lt;state&gt;'s &quot;src&quot;
     * 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";

    /**
     * Error message when the target of the URI fragment in a &lt;state&gt;'s
     * &quot;src&quot; 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";

    /**
     * Error message when the target of the URI fragment in a &lt;state&gt;'s
     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; 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>";

    /**
     * Error message when the target of the URI fragment in a &lt;state&gt;'s
     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
     * the referenced document.
     */
    private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing"
            + " required attribute \"{1}\" value at {2}";

    /**
     * Error message when the target of the URI fragment in a &lt;state&gt;'s
     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; 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.";

    /**
     * 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 &lt;state&gt;'s
     * &quot;src&quot; 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 &lt;state&gt;'s
     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; 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.";

    //--------------------------- XML VOCABULARY ---------------------------//
    //---- ELEMENT NAMES ----//
    private static final String ELEM_ASSIGN = "assign";
    private static final String ELEM_CANCEL = "cancel";
    private static final String ELEM_CONTENT = "content";
    private static final String ELEM_DATA = "data";
    private static final String ELEM_DATAMODEL = "datamodel";
    private static final String ELEM_ELSE = "else";
    private static final String ELEM_ELSEIF = "elseif";
    private static final String ELEM_RAISE = "raise";
    private static final String ELEM_FINAL = "final";
    private static final String ELEM_FINALIZE = "finalize";
    private static final String ELEM_HISTORY = "history";
    private static final String ELEM_IF = "if";
    private static final String ELEM_INITIAL = "initial";
    private static final String ELEM_INVOKE = "invoke";
    private static final String ELEM_FOREACH = "foreach";
    private static final String ELEM_LOG = "log";
    private static final String ELEM_ONENTRY = "onentry";
    private static final String ELEM_ONEXIT = "onexit";
    private static final String ELEM_PARALLEL = "parallel";
    private static final String ELEM_PARAM = "param";
    private static final String ELEM_SCRIPT = "script";
    private static final String ELEM_SCXML = "scxml";
    private static final String ELEM_SEND = "send";
    private static final String ELEM_STATE = "state";
    private static final String ELEM_TRANSITION = "transition";
    private static final String ELEM_VAR = "var";

    //---- ATTRIBUTE NAMES ----//
    private static final String ATTR_ARRAY = "array";
    private static final String ATTR_ATTR = "attr";
    private static final String ATTR_AUTOFORWARD = "autoforward";
    private static final String ATTR_COND = "cond";
    private static final String ATTR_DATAMODEL = "datamodel";
    private static final String ATTR_DELAY = "delay";
    private static final String ATTR_DELAYEXPR = "delayexpr";
    private static final String ATTR_EVENT = "event";
    private static final String ATTR_EVENTEXPR = "eventexpr";
    private static final String ATTR_EXMODE = "exmode";
    private static final String ATTR_EXPR = "expr";
    private static final String ATTR_HINTS = "hints";
    private static final String ATTR_ID = "id";
    private static final String ATTR_IDLOCATION = "idlocation";
    private static final String ATTR_INDEX = "index";
    private static final String ATTR_INITIAL = "initial";
    private static final String ATTR_ITEM = "item";
    private static final String ATTR_LABEL = "label";
    private static final String ATTR_LOCATION = "location";
    private static final String ATTR_NAME = "name";
    private static final String ATTR_NAMELIST = "namelist";
    private static final String ATTR_PROFILE = "profile";
    private static final String ATTR_SENDID = "sendid";
    private static final String ATTR_SENDIDEXPR = "sendidexpr";
    private static final String ATTR_SRC = "src";
    private static final String ATTR_SRCEXPR = "srcexpr";
    private static final String ATTR_TARGET = "target";
    private static final String ATTR_TARGETEXPR = "targetexpr";
    private static final String ATTR_TYPE = "type";
    private static final String ATTR_TYPEEXPR = "typeexpr";
    private static final String ATTR_VERSION = "version";

    //------------------------- 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 {

        return read(scxmlPath, new Configuration());
    }

    /**
     * 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 {

        if (scxmlPath == null) {
            throw new IllegalArgumentException(ERR_NULL_PATH);
        }
        SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null);
        if (scxml != null) {
            ModelUpdater.updateSCXML(scxml);
        }
        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 {

        return read(scxmlURL, new Configuration());
    }

    /**
     * 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 {

        if (scxmlURL == null) {
            throw new IllegalArgumentException(ERR_NULL_URL);
        }
        SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null);
        if (scxml != null) {
            ModelUpdater.updateSCXML(scxml);
        }
        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 {

        return read(scxmlStream, new Configuration());
    }

    /**
     * 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 {

        if (scxmlStream == null) {
            throw new IllegalArgumentException(ERR_NULL_ISTR);
        }
        SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null);
        if (scxml != null) {
            ModelUpdater.updateSCXML(scxml);
        }
        return scxml;
    }

    /**
     * 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}.
     */
    public static SCXML read(final Reader scxmlReader) throws IOException, ModelException, XMLStreamException {

        return read(scxmlReader, new Configuration());
    }

    /**
     * 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 {

        if (scxmlReader == null) {
            throw new IllegalArgumentException(ERR_NULL_READ);
        }
        SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null);
        if (scxml != null) {
            ModelUpdater.updateSCXML(scxml);
        }
        return scxml;
    }

    /**
     * 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}.
     */
    public static SCXML read(final Source scxmlSource) throws IOException, ModelException, XMLStreamException {

        return read(scxmlSource, new Configuration());
    }

    /**
     * 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}.
     */
    public static SCXML read(final Source scxmlSource, final Configuration configuration)
            throws IOException, ModelException, XMLStreamException {

        if (scxmlSource == null) {
            throw new IllegalArgumentException(ERR_NULL_SRC);
        }
        SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource);
        if (scxml != null) {
            ModelUpdater.updateSCXML(scxml);
        }
        return scxml;
    }

    //---------------------- 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}.
     */
    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 {

        if (configuration.pathResolver == null) {
            if (scxmlURL != null) {
                configuration.pathResolver = new URLResolver(scxmlURL);
            } else if (scxmlPath != null) {
                configuration.pathResolver = new URLResolver(new URL(scxmlPath));
            }
        }

        XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader,
                scxmlSource);

        return readDocument(reader, configuration);
    }

    /*
     * Private utility functions for reading the SCXML document.
     */
    /**
     * 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}.
     */
    private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration)
            throws IOException, ModelException, XMLStreamException {

        SCXML scxml = new SCXML();
        while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (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;
    }

    /**
     * Read the contents of this &lt;scxml&gt; 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.
     *
     * @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 readSCXML(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml) throws IOException, ModelException, XMLStreamException {

        scxml.setDatamodelName(readAV(reader, ATTR_DATAMODEL));
        scxml.setExmode(readAV(reader, ATTR_EXMODE));
        scxml.setInitial(readAV(reader, ATTR_INITIAL));
        scxml.setName(readAV(reader, ATTR_NAME));
        scxml.setProfile(readAV(reader, ATTR_PROFILE));
        scxml.setVersion(readRequiredAV(reader, ELEM_SCXML, ATTR_VERSION));
        if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) {
            throw new ModelException(
                    new MessageFormat(ERR_INVALID_VERSION).format(new Object[] { scxml.getVersion() }));
        }
        readNamespaces(configuration, scxml);

        boolean hasGlobalScript = false;

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_STATE.equals(name)) {
                        readState(reader, configuration, scxml, null);
                    } else if (ELEM_PARALLEL.equals(name)) {
                        readParallel(reader, configuration, scxml, null);
                    } else if (ELEM_FINAL.equals(name)) {
                        readFinal(reader, configuration, scxml, null);
                    } else if (ELEM_DATAMODEL.equals(name)) {
                        readDatamodel(reader, configuration, scxml, null);
                    } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
                        readGlobalScript(reader, configuration, scxml);
                        hasGlobalScript = true;
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Read the contents of this &lt;state&gt; 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 state (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 void readState(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml, final TransitionalState parent)
            throws IOException, ModelException, XMLStreamException {

        State state = new State();
        state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE));
        String initial = readAV(reader, ATTR_INITIAL);
        if (initial != null) {
            state.setFirst(initial);
        }
        String src = readAV(reader, ATTR_SRC);
        if (src != null) {
            String source = src;
            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:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_TRANSITION.equals(name)) {
                        state.addTransition(readTransition(reader, configuration));
                    } else if (ELEM_STATE.equals(name)) {
                        readState(reader, configuration, scxml, state);
                    } else if (ELEM_INITIAL.equals(name)) {
                        readInitial(reader, configuration, state);
                    } else if (ELEM_FINAL.equals(name)) {
                        readFinal(reader, configuration, scxml, state);
                    } else if (ELEM_ONENTRY.equals(name)) {
                        readOnEntry(reader, configuration, state);
                    } else if (ELEM_ONEXIT.equals(name)) {
                        readOnExit(reader, configuration, state);
                    } else if (ELEM_PARALLEL.equals(name)) {
                        readParallel(reader, configuration, scxml, state);
                    } else if (ELEM_DATAMODEL.equals(name)) {
                        readDatamodel(reader, configuration, null, state);
                    } else if (ELEM_INVOKE.equals(name)) {
                        readInvoke(reader, configuration, state);
                    } else if (ELEM_HISTORY.equals(name)) {
                        readHistory(reader, configuration, scxml, state);
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Read the contents of this &lt;parallel&gt; 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 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 void readParallel(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml, final TransitionalState parent)
            throws IOException, ModelException, XMLStreamException {

        Parallel parallel = new Parallel();
        parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL));
        String src = readAV(reader, ATTR_SRC);
        if (src != null) {
            String source = src;
            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:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_TRANSITION.equals(name)) {
                        parallel.addTransition(readTransition(reader, configuration));
                    } else if (ELEM_STATE.equals(name)) {
                        readState(reader, configuration, scxml, parallel);
                    } else if (ELEM_PARALLEL.equals(name)) {
                        readParallel(reader, configuration, scxml, parallel);
                    } else if (ELEM_ONENTRY.equals(name)) {
                        readOnEntry(reader, configuration, parallel);
                    } else if (ELEM_ONEXIT.equals(name)) {
                        readOnExit(reader, configuration, parallel);
                    } else if (ELEM_DATAMODEL.equals(name)) {
                        readDatamodel(reader, configuration, null, parallel);
                    } else if (ELEM_INVOKE.equals(name)) {
                        readInvoke(reader, configuration, parallel);
                    } else if (ELEM_HISTORY.equals(name)) {
                        readHistory(reader, configuration, scxml, parallel);
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Read the contents of this &lt;final&gt; 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 end = new Final();
        end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL));

        if (parent == null) {
            scxml.addChild(end);
        } else {
            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:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_ONENTRY.equals(name)) {
                        readOnEntry(reader, configuration, end);
                    } else if (ELEM_ONEXIT.equals(name)) {
                        readOnExit(reader, configuration, end);
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Parse the contents of the SCXML document that this "src" attribute value of a &lt;state&gt; or &lt;parallel&gt;
     * element points to. Without a URL fragment, the entire state machine is imported as contents of the
     * &lt;state&gt; or &lt;parallel&gt;. If a URL fragment is present, the fragment must specify the id of the
     * corresponding &lt;state&gt; or &lt;parallel&gt; to import.
     *
     * @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.
     *
     * @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 IOException, ModelException, XMLStreamException {

        // Check for URI fragment
        String[] fragments = src.split("#", 2);
        String location = fragments[0];
        String fragment = null;
        if (fragments.length > 1) {
            fragment = fragments[1];
        }

        // Parse external document
        SCXML externalSCXML;
        try {
            externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null);
        } catch (Exception e) {
            MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC);
            String errMsg = msgFormat.format(new Object[] { src });
            throw new ModelException(errMsg + " : " + e.getMessage(), e);
        }

        // 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) {
                State s = (State) ts;
                Initial ini = new Initial();
                SimpleTransition t = new SimpleTransition();
                t.setNext(externalSCXML.getInitial());
                ini.setTransition(t);
                s.setInitial(ini);
                for (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
            Object source = externalSCXML.getTargets().get(fragment);
            if (source == null) {
                MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT);
                String errMsg = msgFormat.format(new Object[] { src });
                throw new ModelException(errMsg);
            }
            if (source instanceof State && ts instanceof State) {
                State s = (State) ts;
                State include = (State) source;
                for (OnEntry onentry : include.getOnEntries()) {
                    s.addOnEntry(onentry);
                }
                for (OnExit onexit : include.getOnExits()) {
                    s.addOnExit(onexit);
                }
                s.setDatamodel(include.getDatamodel());
                List<History> histories = include.getHistory();
                for (History h : histories) {
                    s.addHistory(h);
                    configuration.parent.addTarget(h);
                }
                for (EnterableState child : include.getChildren()) {
                    s.addChild(child);
                    configuration.parent.addTarget(child);
                    readInExternalTargets(configuration.parent, child);
                }
                for (Invoke invoke : include.getInvokes()) {
                    s.addInvoke(invoke);
                }
                if (include.getInitial() != null) {
                    s.setInitial(include.getInitial());
                }
                List<Transition> transitions = include.getTransitionsList();
                for (Transition t : transitions) {
                    s.addTransition(t);
                }
            } else if (ts instanceof Parallel && source instanceof Parallel) {
                // TODO src attribute for <parallel>
            } else {
                MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET);
                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
     */
    private static void readInExternalTargets(final SCXML parent, final EnterableState es) {
        if (es instanceof TransitionalState) {
            for (History h : ((TransitionalState) es).getHistory()) {
                parent.addTarget(h);
            }
            for (EnterableState child : ((TransitionalState) es).getChildren()) {
                parent.addTarget(child);
                readInExternalTargets(parent, child);
            }
        }
    }

    /**
     * Read the contents of this &lt;datamodel&gt; 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 readDatamodel(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml, final TransitionalState parent) throws XMLStreamException, ModelException {

        Datamodel dm = new Datamodel();

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_DATA.equals(name)) {
                        readData(reader, configuration, dm);
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }

        if (parent == null) {
            scxml.setDatamodel(dm);
        } else {
            parent.setDatamodel(dm);
        }
    }

    /**
     * Read the contents of this &lt;data&gt; element.
     *
     * @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.
     *
     * @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 {

        Data datum = new Data();
        datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID));
        final String expr = readAV(reader, ATTR_EXPR);
        final String src = readAV(reader, ATTR_SRC);

        if (expr != null) {
            if (src != null) {
                reportConflictingAttribute(reader, configuration, ELEM_DATA, ATTR_EXPR, ATTR_SRC);
            }
            datum.setExpr(expr);
        } else {
            if (src != null) {
                datum.setSrc(src);
            }
        }

        readNamespaces(configuration, datum);
        Node node = readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[] { "id" });
        datum.setNode(node);
        if (node.hasChildNodes()) {
            NodeList children = node.getChildNodes();
            if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
                String text = configuration.contentParser.trimContent(children.item(0).getNodeValue());
                if (configuration.contentParser.hasJsonSignature(text)) {
                    try {
                        datum.setValue(configuration.contentParser.parseJson(text));
                    } catch (IOException e) {
                        throw new ModelException(e);
                    }
                } else {
                    datum.setValue(configuration.contentParser.spaceNormalizeContent(text));
                }
            }
            if (datum.getValue() == null) {
                datum.setValue(node);
            }
        }
        dm.addData(datum);
    }

    /**
     * Read the contents of this &lt;invoke&gt; 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 {@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 void readInvoke(final XMLStreamReader reader, final Configuration configuration,
            final TransitionalState parent) throws XMLStreamException, ModelException {

        Invoke invoke = new Invoke();
        invoke.setId(readAV(reader, ATTR_ID));
        invoke.setSrc(readAV(reader, ATTR_SRC));
        invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR));
        invoke.setType(readAV(reader, ATTR_TYPE));
        invoke.setAutoForward(readBooleanAV(reader, ELEM_INVOKE, ATTR_AUTOFORWARD));
        invoke.setPathResolver(configuration.pathResolver);
        readNamespaces(configuration, invoke);

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_PARAM.equals(name)) {
                        readParam(reader, configuration, invoke);
                    } else if (ELEM_FINALIZE.equals(name)) {
                        readFinalize(reader, configuration, parent, invoke);
                    } else if (ELEM_CONTENT.equals(name)) {
                        readContent(reader, configuration, invoke);
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }

        parent.addInvoke(invoke);
    }

    /**
     * Read the contents of this &lt;param&gt; 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 {@link org.apache.commons.scxml2.model.ParamsContainer} for this param.
     *
     * @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 {

        Param param = new Param();
        param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME));
        String location = readAV(reader, ATTR_LOCATION);
        String expr = readAV(reader, ATTR_EXPR);
        if (expr != null) {
            if (location != null) {
                reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, 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, ELEM_PARAM, ATTR_LOCATION));
        } else {
            param.setLocation(location);
        }
        readNamespaces(configuration, param);
        parent.getParams().add(param);
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;finalize&gt; 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.
     *
     * @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) throws XMLStreamException, ModelException {

        Finalize finalize = new Finalize();
        readExecutableContext(reader, configuration, finalize, null);
        invoke.setFinalize(finalize);
        finalize.setParent(state);
    }

    /**
     * Read the contents of this &lt;content&gt; 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 {

        Content content = new Content();
        content.setExpr(readAV(reader, ATTR_EXPR));
        if (content.getExpr() != null) {
            skipToEndElement(reader);
        } else {
            Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[] {});
            if (body.hasChildNodes()) {
                NodeList children = body.getChildNodes();
                if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
                    content.setBody(children.item(0).getNodeValue());
                } else {
                    content.setBody(body);
                }
            }
        }
        contentContainer.setContent(content);
    }

    /**
     * Read the contents of this &lt;initial&gt; element.
     *
     * @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.
     *
     * @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 readInitial(final XMLStreamReader reader, final Configuration configuration,
            final State state) throws XMLStreamException, ModelException {

        Initial initial = new Initial();

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_TRANSITION.equals(name)) {
                        initial.setTransition(readSimpleTransition(reader, configuration));
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }

        state.setInitial(initial);
    }

    /**
     * Read the contents of this &lt;history&gt; 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 void readHistory(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml, final TransitionalState ts) throws XMLStreamException, ModelException {

        History history = new History();
        history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY));
        history.setType(readAV(reader, ATTR_TYPE));

        ts.addHistory(history);
        scxml.addTarget(history);

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_TRANSITION.equals(name)) {
                        history.setTransition(readTransition(reader, configuration));
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Read the contents of this &lt;onentry&gt; 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 onentry.
     *
     * @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 readOnEntry(final XMLStreamReader reader, final Configuration configuration,
            final EnterableState es) throws XMLStreamException, ModelException {

        OnEntry onentry = new OnEntry();
        onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT));
        readExecutableContext(reader, configuration, onentry, null);
        es.addOnEntry(onentry);
    }

    /**
     * Read the contents of this &lt;onexit&gt; 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.
     *
     * @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) throws XMLStreamException, ModelException {

        OnExit onexit = new OnExit();
        onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT));
        readExecutableContext(reader, configuration, onexit, null);
        es.addOnExit(onexit);
    }

    /**
     * Read the contents of this simple &lt;transition&gt; element.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while 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 SimpleTransition readSimpleTransition(final XMLStreamReader reader,
            final Configuration configuration) throws XMLStreamException, ModelException {

        SimpleTransition transition = new SimpleTransition();
        transition.setNext(readAV(reader, ATTR_TARGET));
        String type = readAV(reader, ATTR_TYPE);
        if (type != null) {
            try {
                transition.setType(TransitionType.valueOf(type));
            } catch (IllegalArgumentException e) {
                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
                String errMsg = msgFormat.format(new Object[] { type, reader.getLocation() });
                throw new ModelException(errMsg);
            }
        }

        readNamespaces(configuration, transition);
        readExecutableContext(reader, configuration, transition, null);

        return transition;
    }

    /**
     * Read the contents of this &lt;transition&gt; element.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while 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 Transition readTransition(final XMLStreamReader reader, final Configuration configuration)
            throws XMLStreamException, ModelException {

        Transition transition = new Transition();
        transition.setCond(readAV(reader, ATTR_COND));
        transition.setEvent(readAV(reader, ATTR_EVENT));
        transition.setNext(readAV(reader, ATTR_TARGET));
        String type = readAV(reader, ATTR_TYPE);
        if (type != null) {
            try {
                transition.setType(TransitionType.valueOf(type));
            } catch (IllegalArgumentException e) {
                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
                String errMsg = msgFormat.format(new Object[] { type, reader.getLocation() });
                throw new ModelException(errMsg);
            }
        }

        readNamespaces(configuration, transition);
        readExecutableContext(reader, configuration, transition, null);

        return transition;
    }

    /**
     * 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} 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 readExecutableContext(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        String end = "";
        if (parent != null) {
            end = parent.getContainerElementName();
        } else if (executable instanceof SimpleTransition) {
            end = ELEM_TRANSITION;
        } else if (executable instanceof OnEntry) {
            end = ELEM_ONENTRY;
        } else if (executable instanceof OnExit) {
            end = ELEM_ONEXIT;
        } else if (executable instanceof Finalize) {
            end = ELEM_FINALIZE;
        }

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_RAISE.equals(name)) {
                        readRaise(reader, configuration, executable, parent);
                    } else if (ELEM_FOREACH.equals(name)) {
                        readForeach(reader, configuration, executable, parent);
                    } else if (ELEM_IF.equals(name)) {
                        readIf(reader, configuration, executable, parent);
                    } else if (ELEM_LOG.equals(name)) {
                        readLog(reader, configuration, executable, parent);
                    } else if (ELEM_ASSIGN.equals(name)) {
                        readAssign(reader, configuration, executable, parent);
                    } else if (ELEM_SEND.equals(name)) {
                        readSend(reader, configuration, executable, parent);
                    } else if (ELEM_CANCEL.equals(name)) {
                        readCancel(reader, configuration, executable, parent);
                    } else if (ELEM_SCRIPT.equals(name)) {
                        readScript(reader, configuration, executable, parent);
                    } else if (ELEM_IF.equals(end) && ELEM_ELSEIF.equals(name)) {
                        readElseIf(reader, configuration, executable, (If) parent);
                    } else if (ELEM_IF.equals(end) && ELEM_ELSE.equals(name)) {
                        readElse(reader, configuration, executable, (If) parent);
                    } else {
                        reportIgnoredElement(reader, configuration, end, nsURI, name);
                    }
                } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) {
                    if (ELEM_VAR.equals(name)) {
                        readVar(reader, configuration, executable, parent);
                    } else {
                        reportIgnoredElement(reader, configuration, end, nsURI, name);
                    }
                } else { // custom action
                    CustomAction customAction = null;
                    if (!configuration.customActions.isEmpty()) {
                        for (CustomAction ca : configuration.customActions) {
                            if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) {
                                customAction = ca;
                            }
                        }
                    }
                    if (customAction != null) {
                        readCustomAction(reader, configuration, customAction, executable, parent);
                    } else {
                        reportIgnoredElement(reader, configuration, end, nsURI, name);
                    }
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }
    }

    /**
     * Read the contents of this &lt;raise&gt; 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 readRaise(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        if (executable instanceof Finalize) {
            // http://www.w3.org/TR/2013/WD-scxml-20130801/#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, ELEM_FINALIZE, XMLNS_SCXML, ELEM_RAISE);
        } else {
            Raise raise = new Raise();
            raise.setEvent(readAV(reader, ATTR_EVENT));
            readNamespaces(configuration, raise);
            raise.setParent(executable);
            if (parent != null) {
                parent.addAction(raise);
            } else {
                executable.addAction(raise);
            }
            skipToEndElement(reader);
        }
    }

    /**
     * Read the contents of this &lt;if&gt; 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 &lt;if&gt; 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 readIf(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        If iff = new If();
        iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND));
        readNamespaces(configuration, iff);
        iff.setParent(executable);
        if (parent != null) {
            parent.addAction(iff);
        } else {
            executable.addAction(iff);
        }
        readExecutableContext(reader, configuration, executable, iff);
    }

    /**
     * Read the contents of this &lt;elseif&gt; 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 iff The parent {@link If} for this &lt;elseif&gt;.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static void readElseIf(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final If iff) throws XMLStreamException, ModelException {

        ElseIf elseif = new ElseIf();
        elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND));
        readNamespaces(configuration, elseif);
        elseif.setParent(executable);
        iff.addAction(elseif);
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;else&gt; 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 iff The parent {@link If} for this &lt;else&gt;.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static void readElse(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final If iff) throws XMLStreamException {

        Else els = new Else();
        readNamespaces(configuration, els);
        els.setParent(executable);
        iff.addAction(els);
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;foreach&gt; 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 &lt;foreach&gt; 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 readForeach(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        Foreach fe = new Foreach();
        fe.setArray(readRequiredAV(reader, ELEM_FOREACH, ATTR_ARRAY));
        fe.setItem(readRequiredAV(reader, ELEM_FOREACH, ATTR_ITEM));
        fe.setIndex(readAV(reader, ATTR_INDEX));
        readNamespaces(configuration, fe);
        fe.setParent(executable);
        if (parent != null) {
            parent.addAction(fe);
        } else {
            executable.addAction(fe);
        }
        readExecutableContext(reader, configuration, executable, fe);
    }

    /**
     * Read the contents of this &lt;log&gt; 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}.
     */
    private static void readLog(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException {

        Log log = new Log();
        log.setExpr(readAV(reader, ATTR_EXPR));
        log.setLabel(readAV(reader, ATTR_LABEL));
        readNamespaces(configuration, log);
        log.setParent(executable);
        if (parent != null) {
            parent.addAction(log);
        } else {
            executable.addAction(log);
        }
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;assign&gt; 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}.
     */
    private static void readAssign(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        Assign assign = new Assign();
        assign.setExpr(readAV(reader, ATTR_EXPR));
        assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION));
        String attrValue = readAV(reader, ATTR_TYPE);
        if (attrValue != null) {
            assign.setType(Evaluator.AssignType.fromValue(attrValue));
            if (assign.getType() == null) {
                reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_TYPE, attrValue);
            }
        }
        attrValue = readAV(reader, ATTR_ATTR);
        if (attrValue != null) {
            if (Evaluator.AssignType.ADD_ATTRIBUTE.equals(assign.getType())) {
                assign.setAttr(attrValue);
            } else {
                reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_ATTR, attrValue);
            }
        }
        assign.setSrc(readAV(reader, ATTR_SRC));
        assign.setPathResolver(configuration.pathResolver);
        readNamespaces(configuration, assign);
        assign.setParent(executable);
        if (parent != null) {
            parent.addAction(assign);
        } else {
            executable.addAction(assign);
        }
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;send&gt; 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) {
            // http://www.w3.org/TR/2013/WD-scxml-20130801/#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, ELEM_FINALIZE, XMLNS_SCXML, ELEM_SEND);
            return;
        }

        Send send = new Send();
        send.setId(readAV(reader, ATTR_ID));
        String attrValue = readAV(reader, ATTR_IDLOCATION);
        if (attrValue != null) {
            if (send.getId() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_ID, ATTR_IDLOCATION);
            } else {
                send.setIdlocation(attrValue);
            }
        }
        send.setDelay(readAV(reader, ATTR_DELAY));
        attrValue = readAV(reader, ATTR_DELAYEXPR);
        if (attrValue != null) {
            if (send.getDelay() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_DELAY, ATTR_DELAYEXPR);
            } else {
                send.setDelayexpr(attrValue);
            }
        }
        send.setEvent(readAV(reader, ATTR_EVENT));
        attrValue = readAV(reader, ATTR_EVENTEXPR);
        if (attrValue != null) {
            if (send.getEvent() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_EVENT, ATTR_EVENTEXPR);
            } else {
                send.setEventexpr(attrValue);
            }
        }
        send.setHints(readAV(reader, ATTR_HINTS));
        send.setNamelist(readAV(reader, ATTR_NAMELIST));
        send.setTarget(readAV(reader, ATTR_TARGET));
        attrValue = readAV(reader, ATTR_TARGETEXPR);
        if (attrValue != null) {
            if (send.getTarget() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TARGET, ATTR_TARGETEXPR);
            } else {
                send.setTargetexpr(attrValue);
            }
        }
        send.setType(readAV(reader, ATTR_TYPE));
        attrValue = readAV(reader, ATTR_TYPEEXPR);
        if (attrValue != null) {
            if (send.getType() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TYPE, ATTR_TYPEEXPR);
            } else {
                send.setTypeexpr(attrValue);
            }
        }
        readNamespaces(configuration, send);

        loop: while (reader.hasNext()) {
            String name, nsURI;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                if (XMLNS_SCXML.equals(nsURI)) {
                    if (ELEM_PARAM.equals(name)) {
                        if (send.getContent() == null) {
                            readParam(reader, configuration, send);
                        } else {
                            reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
                        }
                    } else if (ELEM_CONTENT.equals(name)) {
                        if (send.getNamelist() == null && send.getParams().isEmpty()) {
                            readContent(reader, configuration, send);
                        } else {
                            reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
                        }
                    } else {
                        reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
                    }
                } else {
                    reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
                }
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                break loop;
            default:
            }
        }

        send.setParent(executable);
        if (parent != null) {
            parent.addAction(send);
        } else {
            executable.addAction(send);
        }
    }

    /**
     * Read the contents of this &lt;cancel&gt; 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}.
     */
    private static void readCancel(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException, ModelException {

        Cancel cancel = new Cancel();
        cancel.setSendid(readAV(reader, ATTR_SENDID));
        String attrValue = readAV(reader, ATTR_SENDIDEXPR);
        if (attrValue != null) {
            if (cancel.getSendid() != null) {
                reportConflictingAttribute(reader, configuration, ELEM_CANCEL, ATTR_SENDID, ATTR_SENDIDEXPR);
            } else {
                cancel.setSendidexpr(attrValue);
            }
        }
        readNamespaces(configuration, cancel);
        cancel.setParent(executable);
        if (parent != null) {
            parent.addAction(cancel);
        } else {
            executable.addAction(cancel);
        }
        skipToEndElement(reader);
    }

    /**
     * Read the contents of this &lt;script&gt; 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}.
     */
    private static void readScript(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException {

        Script script = new Script();
        readNamespaces(configuration, script);
        script.setBody(readBody(reader));
        script.setParent(executable);
        if (parent != null) {
            parent.addAction(script);
        } else {
            executable.addAction(script);
        }
    }

    /**
     * Read the contents of the initial &lt;script&gt; element.
     * @see <a href="http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml">
     *     http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml<a> section 3.2.2
     *
     * @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.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static void readGlobalScript(final XMLStreamReader reader, final Configuration configuration,
            final SCXML scxml) throws XMLStreamException {

        Script globalScript = new Script();
        globalScript.setGlobalScript(true);
        readNamespaces(configuration, globalScript);
        globalScript.setBody(readBody(reader));
        scxml.setGlobalScript(globalScript);
    }

    /**
     * Read the contents of this &lt;var&gt; 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}.
     */
    private static void readVar(final XMLStreamReader reader, final Configuration configuration,
            final Executable executable, final ActionsContainer parent) throws XMLStreamException {

        Var var = new Var();
        var.setName(readAV(reader, ATTR_NAME));
        var.setExpr(readAV(reader, ATTR_EXPR));
        readNamespaces(configuration, var);
        var.setParent(executable);
        if (parent != null) {
            parent.addAction(var);
        } else {
            executable.addAction(var);
        }
        skipToEndElement(reader);
    }

    /**
     * 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}.
     */
    private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
            final CustomAction customAction, final Executable executable, final ActionsContainer parent)
            throws XMLStreamException {

        // Instantiate custom action
        Object actionObject;
        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.newInstance();
        } catch (ClassNotFoundException cnfe) {
            throw new XMLStreamException("Cannot find custom action class:" + className, cnfe);
        } catch (IllegalAccessException iae) {
            throw new XMLStreamException("Cannot access custom action class:" + className, iae);
        } catch (InstantiationException 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
        Action action = (Action) actionObject;
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            String name = reader.getAttributeLocalName(i);
            String value = reader.getAttributeValue(i);
            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 (NoSuchMethodException nsme) {
                throw new XMLStreamException("No setter in class:" + className + ", for string property:" + name,
                        nsme);
            } catch (InvocationTargetException ite) {
                throw new XMLStreamException(
                        "Exception calling setter for string property:" + name + " in class:" + className, ite);
            } catch (IllegalAccessException iae) {
                throw new XMLStreamException(
                        "Cannot access setter for string property:" + name + " in class:" + className, iae);
            }
        }

        // Add any body content if necessary
        if (action instanceof ExternalContent) {
            Node body = readNode(reader, configuration, customAction.getNamespaceURI(), customAction.getLocalName(),
                    new String[] {});
            NodeList childNodes = body.getChildNodes();
            List<Node> externalNodes = ((ExternalContent) action).getExternalNodes();
            for (int i = 0; i < childNodes.getLength(); i++) {
                externalNodes.add(childNodes.item(i));
            }
        } else {
            skipToEndElement(reader);
        }

        // Wire in the action and add to parent
        readNamespaces(configuration, action);
        action.setParent(executable);
        if (parent != null) {
            parent.addAction(action);
        } else {
            executable.addAction(action);
        }
    }

    /**
     * Read the following contents into a DOM {@link Node}.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while parsing.
     * @param namespaceURI The namespace URI of the parent element
     * @param localName The local name of the parent element
     * @param attrs The attributes that will be read into the root DOM node.
     *
     * @return The parsed content as a DOM {@link Node}.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static Node readNode(final XMLStreamReader reader, final Configuration configuration,
            final String namespaceURI, final String localName, final String[] attrs) throws XMLStreamException {

        // Create a document in which to build the DOM node
        Document document;
        try {
            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException pce) {
            throw new XMLStreamException(ERR_PARSER_CFG);
        }

        // This root element will be returned, add any attributes as specified
        Element root = document.createElementNS(namespaceURI, localName);
        for (final String attr1 : attrs) {
            Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attr1);
            attr.setValue(readAV(reader, attr1));
            root.setAttributeNodeNS(attr);
        }
        document.appendChild(root);

        boolean children = false;
        Node parent = root;

        // Convert stream to DOM node(s) while maintaining parent child relationships
        loop: while (reader.hasNext()) {
            String name, nsURI;
            Node child = null;
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                if (!children && root.hasChildNodes()) {
                    // remove any children
                    root.setTextContent(null);
                }
                children = true;
                pushNamespaces(reader, configuration);
                nsURI = reader.getNamespaceURI();
                name = reader.getLocalName();
                Element elem = document.createElementNS(nsURI, name);
                for (int i = 0; i < reader.getAttributeCount(); i++) {
                    nsURI = reader.getAttributeNamespace(i);
                    name = reader.getAttributeLocalName(i);
                    String prefix = reader.getAttributePrefix(i);
                    if (prefix != null && prefix.length() > 0) {
                        name = prefix + ":" + name;
                    }
                    Attr attr = document.createAttributeNS(nsURI, name);
                    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:
                children = true;
                child = document.createCDATASection(reader.getText());
                break;
            case XMLStreamConstants.COMMENT:
                children = true;
                child = document.createComment(reader.getText());
                break;
            case XMLStreamConstants.END_ELEMENT:
                popNamespaces(reader, configuration);
                parent = parent.getParentNode();
                if (parent == document) {
                    break loop;
                }
                break;
            default: // rest is ignored
            }
            if (child != null) {
                parent.appendChild(child);
            }
        }
        if (!children && root.hasChildNodes()) {
            root.setTextContent(root.getTextContent().trim());
        }
        return root;
    }

    /**
     * Read the following body contents into a String.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     *
     * @return The body content read into a String.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static String readBody(final XMLStreamReader reader) throws XMLStreamException {

        StringBuilder body = new StringBuilder();
        org.apache.commons.logging.Log log;

        // Add all body content to StringBuilder
        loop: while (reader.hasNext()) {
            switch (reader.next()) {
            case XMLStreamConstants.START_ELEMENT:
                log = LogFactory.getLog(SCXMLReader.class);
                log.warn("Ignoring XML content in <script> element, encountered element with local name: "
                        + reader.getLocalName());
                skipToEndElement(reader);
                break;
            case XMLStreamConstants.SPACE:
            case XMLStreamConstants.CHARACTERS:
            case XMLStreamConstants.ENTITY_REFERENCE:
            case XMLStreamConstants.CDATA:
            case XMLStreamConstants.COMMENT:
                body.append(reader.getText());
                break;
            case XMLStreamConstants.END_ELEMENT:
                break loop;
            default: // rest is ignored
            }
        }
        return body.toString();
    }

    /**
     * @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(String input) {
        return input == null || input.trim().length() == 0 ? null : input.trim();
    }

    /**
     * Get the attribute value at the current reader location.
     *
     * @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.
     */
    private static String readAV(final XMLStreamReader reader, final String attrLocalName) {
        return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
    }

    /**
     * Get the Boolean attribute value at the current reader location.
     *
     * @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.
     *
     * @return The Boolean value of the attribute.
     * @throws ModelException When the attribute value is not empty but neither "true" or "false".
     */
    private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName,
            final String attrLocalName) throws ModelException {
        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
        Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
        if (result == null && value != null) {
            MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN);
            String errMsg = msgFormat
                    .format(new Object[] { value, attrLocalName, elementName, reader.getLocation() });
            throw new ModelException(errMsg);
        }
        return result;
    }

    /**
     * Get a required attribute value at the current reader location,
     *
     * @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.
     *
     * @return The value of the attribute.
     * @throws ModelException When the required attribute is missing or empty.
     */
    private static String readRequiredAV(final XMLStreamReader reader, final String elementName,
            final String attrLocalName) throws ModelException {
        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
        if (value == null) {
            MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING);
            String errMsg = msgFormat.format(new Object[] { elementName, attrLocalName, reader.getLocation() });
            throw new ModelException(errMsg);
        }
        return value;
    }

    private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml,
            final String elementName) throws ModelException {
        String id = readAV(reader, ATTR_ID);
        if (id == null) {
            id = scxml.generateTransitionTargetId();
        } else if (id.startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
            MessageFormat msgFormat = new MessageFormat(ERR_RESERVED_ID_PREFIX);
            String errMsg = msgFormat.format(new Object[] { elementName, id, reader.getLocation() });
            throw new ModelException(errMsg);
        }
        return id;
    }

    /**
     * Read the current active namespace declarations into the namespace prefixes holder.
     *
     * @param configuration The {@link Configuration} to use while parsing.
     * @param holder The {@link NamespacePrefixesHolder} to populate.
     */
    private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) {

        holder.setNamespaces(configuration.getCurrentNamespaces());
    }

    /**
     * Report an ignored element via the {@link XMLReporter} if available and the class
     * {@link org.apache.commons.logging.Log}.
     *
     * @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.
     *
     * @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) throws XMLStreamException, ModelException {

        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
        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() && log.isWarnEnabled()) {
            log.warn(sb.toString());
        }
        if (configuration.isStrict()) {
            throw new ModelException(sb.toString());
        }
        XMLReporter reporter = configuration.reporter;
        if (reporter != null) {
            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
        }
        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
     */
    private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException {
        int elementsToSkip = 1;
        while (elementsToSkip > 0 && reader.hasNext()) {
            int next = reader.next();
            if (next == XMLStreamConstants.START_ELEMENT) {
                elementsToSkip++;
            } else if (next == XMLStreamConstants.END_ELEMENT) {
                elementsToSkip--;
            }
        }
    }

    /**
     * Report an ignored attribute via the {@link XMLReporter} if available and the class
     * {@link org.apache.commons.logging.Log}.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while parsing.
     * @param element The element name.
     * @param attr The attribute which is ignored.
     * @param value The value of the attribute which is ignored.
     *
     * @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 reportIgnoredAttribute(final XMLStreamReader reader, final Configuration configuration,
            final String element, final String attr, final String value) throws XMLStreamException, ModelException {

        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
        StringBuilder sb = new StringBuilder();
        sb.append("Ignoring unknown or invalid <").append(element).append("> attribute ").append(attr).append("=\"")
                .append(value).append("\" at ").append(reader.getLocation());
        if (!configuration.isSilent() && log.isWarnEnabled()) {
            log.warn(sb.toString());
        }
        if (configuration.isStrict()) {
            throw new ModelException(sb.toString());
        }
        XMLReporter reporter = configuration.reporter;
        if (reporter != null) {
            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
        }
    }

    /**
     * Report a conflicting attribute via the {@link XMLReporter} if available and the class
     * {@link org.apache.commons.logging.Log}.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while parsing.
     * @param element The element name.
     * @param attr The attribute with which a conflict is detected.
     * @param conflictingAttr The conflicting attribute
     *
     * @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 reportConflictingAttribute(final XMLStreamReader reader, final Configuration configuration,
            final String element, final String attr, final String conflictingAttr)
            throws XMLStreamException, ModelException {

        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
        StringBuilder sb = new StringBuilder();
        sb.append("Ignoring <").append(element).append("> attribute \"").append(conflictingAttr)
                .append("\" which conflicts with already defined attribute \"").append(attr).append("\" at ")
                .append(reader.getLocation());
        if (!configuration.isSilent() && log.isWarnEnabled()) {
            log.warn(sb.toString());
        }
        if (configuration.isStrict()) {
            throw new ModelException(sb.toString());
        }
        XMLReporter reporter = configuration.reporter;
        if (reporter != null) {
            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
        }
    }

    /**
     * Push any new namespace declarations on the configuration namespaces map.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while parsing.
     */
    private static void pushNamespaces(final XMLStreamReader reader, final Configuration configuration) {

        for (int i = 0; i < reader.getNamespaceCount(); i++) {
            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
            if (stack == null) {
                stack = new Stack<String>();
                configuration.namespaces.put(reader.getNamespacePrefix(i), stack);
            }
            stack.push(reader.getNamespaceURI(i));
        }
    }

    /**
     * Pop any expiring namespace declarations from the configuration namespaces map.
     *
     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
     * @param configuration The {@link Configuration} to use while parsing.
     *
     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
     */
    private static void popNamespaces(final XMLStreamReader reader, final Configuration configuration)
            throws XMLStreamException {

        for (int i = 0; i < reader.getNamespaceCount(); i++) {
            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
            if (stack == null) {
                throw new XMLStreamException("Configuration namespaces stack null");
            }
            try {
                stack.pop();
                if (stack.empty()) {
                    configuration.namespaces.remove(reader.getNamespacePrefix(i));
                }
            } catch (EmptyStackException e) {
                throw new XMLStreamException("Configuration namespaces stack popped too many times");
            }
        }
    }

    /**
     * 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 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.
     *
     * @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 XMLStreamReader getReader(final Configuration configuration, final URL url, final String path,
            final InputStream stream, final Reader reader, final Source source)
            throws IOException, XMLStreamException {

        // 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);
        for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
            factory.setProperty(property.getKey(), property.getValue());
        }
        factory.setXMLReporter(configuration.reporter);
        factory.setXMLResolver(configuration.resolver);

        // Consolidate InputStream options
        InputStream urlStream = null;
        if (url != null || path != null) {
            URL scxml = (url != null ? url : new URL(path));
            URLConnection conn = scxml.openConnection();
            conn.setUseCaches(false);
            urlStream = conn.getInputStream();
        } else if (stream != null) {
            urlStream = stream;
        }

        // Create the XMLStreamReader
        XMLStreamReader xsr = null;

        if (configuration.validate) {
            // Validation requires us to use a Source

            URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location
            SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            Schema schema;
            try {
                schema = schemaFactory.newSchema(scxmlSchema);
            } catch (SAXException se) {
                throw new XMLStreamException("Failed to create SCXML Schema for validation", se);
            }

            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 (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;
    }

    /**
     * Discourage instantiation since this is a utility class.
     */
    private SCXMLReader() {
        super();
    }

    //------------------------- CONFIGURATION CLASS -------------------------//
    /**
     * <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>
     */
    public static class Configuration {

        /*
         * Configuration properties for this {@link SCXMLReader}.
         */
        // XMLInputFactory configuration properties.
        /**
         * The <code>factoryId</code> to use for the {@link XMLInputFactory}.
         */
        final String factoryId;

        /**
         * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
         */
        final ClassLoader factoryClassLoader;

        /**
         * The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
         */
        final XMLEventAllocator allocator;

        /**
         * The map of properties (keys are property name strings, values are object property values) for the
         * {@link XMLInputFactory}.
         */
        final Map<String, Object> properties;

        /**
         * The {@link XMLResolver} for the {@link XMLInputFactory}.
         */
        final XMLResolver resolver;

        /**
         * The {@link XMLReporter} for the {@link XMLInputFactory}.
         */
        final XMLReporter reporter;

        // XMLStreamReader configuration properties.
        /**
         * The <code>encoding</code> to use for the {@link XMLStreamReader}.
         */
        final String encoding;

        /**
         * The <code>systemId</code> to use for the {@link XMLStreamReader}.
         */
        final String systemId;

        /**
         * Whether to validate the input with the XML Schema for SCXML.
         */
        final boolean validate;

        // Commons SCXML object model configuration properties.
        /**
         * The list of Commons SCXML custom actions that will be available for this document.
         */
        final List<CustomAction> customActions;

        /**
         * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create.
         */
        final ClassLoader customActionClassLoader;

        /**
         * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes.
         */
        final boolean useContextClassLoaderForCustomActions;

        /**
         * The map for bookkeeping the current active namespace declarations. The keys are prefixes and the values are
         * {@link Stack}s containing the corresponding namespaceURIs, with the active one on top.
         */
        final Map<String, Stack<String>> namespaces;

        // Mutable Commons SCXML object model configuration properties.
        /**
         * The parent SCXML document if this document is src'ed in via the &lt;state&gt; or &lt;parallel&gt; element's
         * "src" attribute.
         */
        SCXML parent;

        /**
         * The Commons SCXML {@link PathResolver} to use for this document.
         */
        PathResolver pathResolver;

        /**
         * Whether to silently ignore any unknown or invalid elements
         * or to leave warning logs for those.
         */
        boolean silent;

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

        ContentParser contentParser;

        /*
         * Public constructors
         */
        /**
         * Default constructor.
         */
        public Configuration() {
            this(null, null);
        }

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

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

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

        /*
         * 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 &lt;state&gt; or
         *               &lt;parallel&gt; 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);
        }

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

        /**
         * 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 &lt;state&gt; or
         *               &lt;parallel&gt; 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);
        }

        /**
         * 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 &lt;state&gt; or
         *               &lt;parallel&gt; 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<String, Object>() : 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<CustomAction>() : customActions);
            this.customActionClassLoader = customActionClassLoader;
            this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions;
            this.namespaces = new HashMap<String, Stack<String>>();
            this.silent = silent;
            this.strict = strict;
            this.contentParser = new ContentParser();
        }

        /*
         * Package access convenience methods
         */
        /**
         * Get the current namespaces at this point in the StAX reading.
         *
         * @return Map<String,String> The namespace map (keys are prefixes and values are the corresponding current
         *                            namespace URIs).
         */
        Map<String, String> getCurrentNamespaces() {
            Map<String, String> currentNamespaces = new HashMap<String, String>();
            for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) {
                currentNamespaces.put(nsEntry.getKey(), nsEntry.getValue().peek());
            }
            return currentNamespaces;
        }

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

        /**
         * 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(boolean silent) {
            this.silent = silent;
        }

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

        /**
         * 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(boolean strict) {
            this.strict = strict;
        }
    }
}