001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.io;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.Reader;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.net.URL;
025import java.net.URLConnection;
026import java.text.MessageFormat;
027import java.util.ArrayList;
028import java.util.EmptyStackException;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Stack;
033
034import javax.xml.parsers.DocumentBuilderFactory;
035import javax.xml.parsers.ParserConfigurationException;
036import javax.xml.stream.XMLInputFactory;
037import javax.xml.stream.XMLReporter;
038import javax.xml.stream.XMLResolver;
039import javax.xml.stream.XMLStreamConstants;
040import javax.xml.stream.XMLStreamException;
041import javax.xml.stream.XMLStreamReader;
042import javax.xml.stream.util.XMLEventAllocator;
043import javax.xml.transform.Source;
044import javax.xml.transform.stream.StreamSource;
045import javax.xml.validation.Schema;
046import javax.xml.validation.SchemaFactory;
047import javax.xml.validation.Validator;
048
049import org.apache.commons.logging.LogFactory;
050import org.apache.commons.scxml2.Evaluator;
051import org.apache.commons.scxml2.PathResolver;
052import org.apache.commons.scxml2.env.SimpleErrorHandler;
053import org.apache.commons.scxml2.env.URLResolver;
054import org.apache.commons.scxml2.model.Action;
055import org.apache.commons.scxml2.model.ActionsContainer;
056import org.apache.commons.scxml2.model.Assign;
057import org.apache.commons.scxml2.model.Cancel;
058import org.apache.commons.scxml2.model.Content;
059import org.apache.commons.scxml2.model.ContentContainer;
060import org.apache.commons.scxml2.model.CustomAction;
061import org.apache.commons.scxml2.model.Data;
062import org.apache.commons.scxml2.model.Datamodel;
063import org.apache.commons.scxml2.model.Else;
064import org.apache.commons.scxml2.model.ElseIf;
065import org.apache.commons.scxml2.model.EnterableState;
066import org.apache.commons.scxml2.model.Executable;
067import org.apache.commons.scxml2.model.ExternalContent;
068import org.apache.commons.scxml2.model.Final;
069import org.apache.commons.scxml2.model.Finalize;
070import org.apache.commons.scxml2.model.Foreach;
071import org.apache.commons.scxml2.model.History;
072import org.apache.commons.scxml2.model.If;
073import org.apache.commons.scxml2.model.Initial;
074import org.apache.commons.scxml2.model.Invoke;
075import org.apache.commons.scxml2.model.Log;
076import org.apache.commons.scxml2.model.ModelException;
077import org.apache.commons.scxml2.model.NamespacePrefixesHolder;
078import org.apache.commons.scxml2.model.OnEntry;
079import org.apache.commons.scxml2.model.OnExit;
080import org.apache.commons.scxml2.model.Parallel;
081import org.apache.commons.scxml2.model.Param;
082import org.apache.commons.scxml2.model.ParamsContainer;
083import org.apache.commons.scxml2.model.Raise;
084import org.apache.commons.scxml2.model.SCXML;
085import org.apache.commons.scxml2.model.Script;
086import org.apache.commons.scxml2.model.Send;
087import org.apache.commons.scxml2.model.SimpleTransition;
088import org.apache.commons.scxml2.model.State;
089import org.apache.commons.scxml2.model.Transition;
090import org.apache.commons.scxml2.model.TransitionType;
091import org.apache.commons.scxml2.model.TransitionalState;
092import org.apache.commons.scxml2.model.Var;
093import org.w3c.dom.Attr;
094import org.w3c.dom.Document;
095import org.w3c.dom.Element;
096import org.w3c.dom.Node;
097import org.w3c.dom.NodeList;
098import org.xml.sax.SAXException;
099
100/**
101 * <p>The SCXMLReader provides the ability to read a SCXML document into
102 * the Java object model provided in the model package.</p>
103 *
104 * <p>See latest version of the SCXML Working Draft for more details.</p>
105 *
106 * <p><b>NOTE:</b> The SCXMLReader assumes that the SCXML document to be
107 * parsed is well-formed and correct. If that assumption does not hold,
108 * any subsequent behavior is undefined.</p>
109 *
110 * @since 1.0
111 */
112public final class SCXMLReader {
113
114    //---------------------- PRIVATE CONSTANTS ----------------------//
115    //---- NAMESPACES ----//
116    /**
117     * The SCXML namespace that this Reader is built for. Any document
118     * that is intended to be parsed by this reader <b>must</b>
119     * bind the SCXML elements to this namespace.
120     */
121    private static final String XMLNS_SCXML =
122            "http://www.w3.org/2005/07/scxml";
123
124    /**
125     * The namespace that defines any custom actions defined by the Commons
126     * SCXML implementation. Any document that intends to use these custom
127     * actions needs to ensure that they are in the correct namespace. Use
128     * of actions in this namespace makes the document non-portable across
129     * implementations.
130     */
131    private static final String XMLNS_COMMONS_SCXML =
132            "http://commons.apache.org/scxml";
133
134    /**
135     * The version attribute value the SCXML element <em>must</em> have as stated by the spec: 3.2.1
136     */
137    private static final String SCXML_REQUIRED_VERSION = "1.0";
138    /**
139     * The default namespace for attributes.
140     */
141    private static final String XMLNS_DEFAULT = null;
142
143    //---- ERROR MESSAGES ----//
144    /**
145     * Null URL passed as argument.
146     */
147    private static final String ERR_NULL_URL = "Cannot parse null URL";
148
149    /**
150     * Null path passed as argument.
151     */
152    private static final String ERR_NULL_PATH = "Cannot parse null path";
153
154    /**
155     * Null InputStream passed as argument.
156     */
157    private static final String ERR_NULL_ISTR = "Cannot parse null InputStream";
158
159    /**
160     * Null Reader passed as argument.
161     */
162    private static final String ERR_NULL_READ = "Cannot parse null Reader";
163
164    /**
165     * Null Source passed as argument.
166     */
167    private static final String ERR_NULL_SRC = "Cannot parse null Source";
168
169    /**
170     * Error message while attempting to define a custom action which does
171     * not extend the Commons SCXML Action base class.
172     */
173    private static final String ERR_CUSTOM_ACTION_TYPE = "Custom actions list"
174            + " contained unknown object, class not a Commons SCXML Action class subtype: ";
175
176    /**
177     * Parser configuration error while trying to parse stream to DOM node(s).
178     */
179    private static final String ERR_PARSER_CFG = "ParserConfigurationException while trying"
180            + " to parse stream into DOM node(s).";
181
182    /**
183     * Error message when the URI in a &lt;state&gt;'s &quot;src&quot;
184     * attribute does not point to a valid SCXML document, and thus cannot be
185     * parsed.
186     */
187    private static final String ERR_STATE_SRC =
188            "Source attribute in <state src=\"{0}\"> cannot be parsed";
189
190    /**
191     * Error message when the target of the URI fragment in a &lt;state&gt;'s
192     * &quot;src&quot; attribute is not defined in the referenced document.
193     */
194    private static final String ERR_STATE_SRC_FRAGMENT = "URI Fragment in "
195            + "<state src=\"{0}\"> is an unknown state in referenced document";
196
197    /**
198     * Error message when the target of the URI fragment in a &lt;state&gt;'s
199     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
200     * the referenced document.
201     */
202    private static final String ERR_STATE_SRC_FRAGMENT_TARGET = "URI Fragment"
203            + " in <state src=\"{0}\"> does not point to a <state> or <final>";
204
205    /**
206     * Error message when the target of the URI fragment in a &lt;state&gt;'s
207     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
208     * the referenced document.
209     */
210    private static final String ERR_REQUIRED_ATTRIBUTE_MISSING = "<{0}> is missing"
211            +" required attribute \"{1}\" value at {2}";
212
213    /**
214     * Error message when the target of the URI fragment in a &lt;state&gt;'s
215     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
216     * the referenced document.
217     */
218    private static final String ERR_ATTRIBUTE_NOT_BOOLEAN = "Illegal value \"{0}\""
219            + "for attribute \"{1}\" in element <{2}> at {3}."
220            +" Only the value \"true\" or \"false\" is allowed.";
221
222    /**
223     * Error message when the element (state|parallel|final|history) uses an id value
224     * with the reserved prefix {@link SCXML#GENERATED_TT_ID_PREFIX}.
225     */
226    private static final String ERR_RESERVED_ID_PREFIX = "Reserved id prefix \""
227            +SCXML.GENERATED_TT_ID_PREFIX+"\" used for <{0} id=\"{1}\"> at {2}";
228
229    /**
230     * Error message when the target of the URI fragment in a &lt;state&gt;'s
231     * &quot;src&quot; attribute is not defined in the referenced document.
232     */
233    private static final String ERR_UNSUPPORTED_TRANSITION_TYPE = "Unsupported transition type "
234            + "for <transition type=\"{0}\"> at {1}.";
235
236    /**
237     * Error message when the target of the URI fragment in a &lt;state&gt;'s
238     * &quot;src&quot; attribute is not a &lt;state&gt; or &lt;final&gt; in
239     * the referenced document.
240     */
241    private static final String ERR_INVALID_VERSION = "The <scxml> element defines"
242            +" an unsupported version \"{0}\", only version \"1.0\" is supported.";
243
244    //--------------------------- XML VOCABULARY ---------------------------//
245    //---- ELEMENT NAMES ----//
246    private static final String ELEM_ASSIGN = "assign";
247    private static final String ELEM_CANCEL = "cancel";
248    private static final String ELEM_CONTENT = "content";
249    private static final String ELEM_DATA = "data";
250    private static final String ELEM_DATAMODEL = "datamodel";
251    private static final String ELEM_ELSE = "else";
252    private static final String ELEM_ELSEIF = "elseif";
253    private static final String ELEM_RAISE = "raise";
254    private static final String ELEM_FINAL = "final";
255    private static final String ELEM_FINALIZE = "finalize";
256    private static final String ELEM_HISTORY = "history";
257    private static final String ELEM_IF = "if";
258    private static final String ELEM_INITIAL = "initial";
259    private static final String ELEM_INVOKE = "invoke";
260    private static final String ELEM_FOREACH = "foreach";
261    private static final String ELEM_LOG = "log";
262    private static final String ELEM_ONENTRY = "onentry";
263    private static final String ELEM_ONEXIT = "onexit";
264    private static final String ELEM_PARALLEL = "parallel";
265    private static final String ELEM_PARAM = "param";
266    private static final String ELEM_SCRIPT = "script";
267    private static final String ELEM_SCXML = "scxml";
268    private static final String ELEM_SEND = "send";
269    private static final String ELEM_STATE = "state";
270    private static final String ELEM_TRANSITION = "transition";
271    private static final String ELEM_VAR = "var";
272
273    //---- ATTRIBUTE NAMES ----//
274    private static final String ATTR_ARRAY = "array";
275    private static final String ATTR_ATTR = "attr";
276    private static final String ATTR_AUTOFORWARD = "autoforward";
277    private static final String ATTR_COND = "cond";
278    private static final String ATTR_DATAMODEL = "datamodel";
279    private static final String ATTR_DELAY = "delay";
280    private static final String ATTR_DELAYEXPR = "delayexpr";
281    private static final String ATTR_EVENT = "event";
282    private static final String ATTR_EVENTEXPR = "eventexpr";
283    private static final String ATTR_EXMODE = "exmode";
284    private static final String ATTR_EXPR = "expr";
285    private static final String ATTR_HINTS = "hints";
286    private static final String ATTR_ID = "id";
287    private static final String ATTR_IDLOCATION = "idlocation";
288    private static final String ATTR_INDEX = "index";
289    private static final String ATTR_INITIAL = "initial";
290    private static final String ATTR_ITEM = "item";
291    private static final String ATTR_LABEL = "label";
292    private static final String ATTR_LOCATION = "location";
293    private static final String ATTR_NAME = "name";
294    private static final String ATTR_NAMELIST = "namelist";
295    private static final String ATTR_PROFILE = "profile";
296    private static final String ATTR_SENDID = "sendid";
297    private static final String ATTR_SENDIDEXPR = "sendidexpr";
298    private static final String ATTR_SRC = "src";
299    private static final String ATTR_SRCEXPR = "srcexpr";
300    private static final String ATTR_TARGET = "target";
301    private static final String ATTR_TARGETEXPR = "targetexpr";
302    private static final String ATTR_TYPE = "type";
303    private static final String ATTR_TYPEEXPR = "typeexpr";
304    private static final String ATTR_VERSION = "version";
305
306    //------------------------- PUBLIC API METHODS -------------------------//
307    /*
308     * Public methods
309     */
310    /**
311     * Parse the SCXML document at the supplied path.
312     *
313     * @param scxmlPath The real path to the SCXML document.
314     *
315     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
316     *
317     * @throws IOException An IO error during parsing.
318     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
319     *                        errors in the SCXML document that may not be identified by the schema).
320     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
321     */
322    public static SCXML read(final String scxmlPath)
323            throws IOException, ModelException, XMLStreamException {
324
325        return read(scxmlPath, new Configuration());
326    }
327
328    /**
329     * Parse the SCXML document at the supplied path with the given {@link Configuration}.
330     *
331     * @param scxmlPath The real path to the SCXML document.
332     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
333     *
334     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
335     *
336     * @throws IOException An IO error during parsing.
337     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
338     *                        errors in the SCXML document that may not be identified by the schema).
339     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
340     */
341    public static SCXML read(final String scxmlPath, final Configuration configuration)
342            throws IOException, ModelException, XMLStreamException {
343
344        if (scxmlPath == null) {
345            throw new IllegalArgumentException(ERR_NULL_PATH);
346        }
347        SCXML scxml = readInternal(configuration, null, scxmlPath, null, null, null);
348        if (scxml != null) {
349            ModelUpdater.updateSCXML(scxml);
350        }
351        return scxml;
352    }
353
354    /**
355     * Parse the SCXML document at the supplied {@link URL}.
356     *
357     * @param scxmlURL The SCXML document {@link URL} to parse.
358     *
359     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
360     *
361     * @throws IOException An IO error during parsing.
362     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
363     *                        errors in the SCXML document that may not be identified by the schema).
364     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
365     */
366    public static SCXML read(final URL scxmlURL)
367            throws IOException, ModelException, XMLStreamException {
368
369        return read(scxmlURL, new Configuration());
370    }
371
372    /**
373     * Parse the SCXML document at the supplied {@link URL} with the given {@link Configuration}.
374     *
375     * @param scxmlURL The SCXML document {@link URL} to parse.
376     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
377     *
378     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
379     *
380     * @throws IOException An IO error during parsing.
381     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
382     *                        errors in the SCXML document that may not be identified by the schema).
383     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
384     */
385    public static SCXML read(final URL scxmlURL, final Configuration configuration)
386            throws IOException, ModelException, XMLStreamException {
387
388        if (scxmlURL == null) {
389            throw new IllegalArgumentException(ERR_NULL_URL);
390        }
391        SCXML scxml = readInternal(configuration, scxmlURL, null, null, null, null);
392        if (scxml != null) {
393            ModelUpdater.updateSCXML(scxml);
394        }
395        return scxml;
396    }
397
398    /**
399     * Parse the SCXML document supplied by the given {@link InputStream}.
400     *
401     * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
402     *
403     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
404     *
405     * @throws IOException An IO error during parsing.
406     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
407     *                        errors in the SCXML document that may not be identified by the schema).
408     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
409     */
410    public static SCXML read(final InputStream scxmlStream)
411            throws IOException, ModelException, XMLStreamException {
412
413        return read(scxmlStream, new Configuration());
414    }
415
416    /**
417     * Parse the SCXML document supplied by the given {@link InputStream} with the given {@link Configuration}.
418     *
419     * @param scxmlStream The {@link InputStream} supplying the SCXML document to parse.
420     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
421     *
422     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
423     *
424     * @throws IOException An IO error during parsing.
425     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
426     *                        errors in the SCXML document that may not be identified by the schema).
427     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
428     */
429    public static SCXML read(final InputStream scxmlStream, final Configuration configuration)
430            throws IOException, ModelException, XMLStreamException {
431
432        if (scxmlStream == null) {
433            throw new IllegalArgumentException(ERR_NULL_ISTR);
434        }
435        SCXML scxml = readInternal(configuration, null, null, scxmlStream, null, null);
436        if (scxml != null) {
437            ModelUpdater.updateSCXML(scxml);
438        }
439        return scxml;
440    }
441
442    /**
443     * Parse the SCXML document supplied by the given {@link Reader}.
444     *
445     * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
446     *
447     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
448     *
449     * @throws IOException An IO error during parsing.
450     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
451     *                        errors in the SCXML document that may not be identified by the schema).
452     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
453     */
454    public static SCXML read(final Reader scxmlReader)
455            throws IOException, ModelException, XMLStreamException {
456
457        return read(scxmlReader, new Configuration());
458    }
459
460    /**
461     * Parse the SCXML document supplied by the given {@link Reader} with the given {@link Configuration}.
462     *
463     * @param scxmlReader The {@link Reader} supplying the SCXML document to parse.
464     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
465     *
466     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
467     *
468     * @throws IOException An IO error during parsing.
469     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
470     *                        errors in the SCXML document that may not be identified by the schema).
471     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
472     */
473    public static SCXML read(final Reader scxmlReader, final Configuration configuration)
474            throws IOException, ModelException, XMLStreamException {
475
476        if (scxmlReader == null) {
477            throw new IllegalArgumentException(ERR_NULL_READ);
478        }
479        SCXML scxml = readInternal(configuration, null, null, null, scxmlReader, null);
480        if (scxml != null) {
481            ModelUpdater.updateSCXML(scxml);
482        }
483        return scxml;
484    }
485
486    /**
487     * Parse the SCXML document supplied by the given {@link Source}.
488     *
489     * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
490     *
491     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
492     *
493     * @throws IOException An IO error during parsing.
494     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
495     *                        errors in the SCXML document that may not be identified by the schema).
496     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
497     */
498    public static SCXML read(final Source scxmlSource)
499            throws IOException, ModelException, XMLStreamException {
500
501        return read(scxmlSource, new Configuration());
502    }
503
504    /**
505     * Parse the SCXML document supplied by the given {@link Source} with the given {@link Configuration}.
506     *
507     * @param scxmlSource The {@link Source} supplying the SCXML document to parse.
508     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
509     *
510     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document.
511     *
512     * @throws IOException An IO error during parsing.
513     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
514     *                        errors in the SCXML document that may not be identified by the schema).
515     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
516     */
517    public static SCXML read(final Source scxmlSource, final Configuration configuration)
518            throws IOException, ModelException, XMLStreamException {
519
520        if (scxmlSource == null) {
521            throw new IllegalArgumentException(ERR_NULL_SRC);
522        }
523        SCXML scxml = readInternal(configuration, null, null, null, null, scxmlSource);
524        if (scxml != null) {
525            ModelUpdater.updateSCXML(scxml);
526        }
527        return scxml;
528    }
529
530    //---------------------- PRIVATE UTILITY METHODS ----------------------//
531    /**
532     * Parse the SCXML document at the supplied {@link URL} using the supplied {@link Configuration}, but do not
533     * wire up the object model to be usable just yet. Exactly one of the url, path, stream, reader or source
534     * parameters must be provided.
535     *
536     * @param configuration The {@link Configuration} to use when parsing the SCXML document.
537     * @param scxmlURL The optional SCXML document {@link URL} to parse.
538     * @param scxmlPath The optional real path to the SCXML document as a string.
539     * @param scxmlStream The optional {@link InputStream} providing the SCXML document.
540     * @param scxmlReader The optional {@link Reader} providing the SCXML document.
541     * @param scxmlSource The optional {@link Source} providing the SCXML document.
542     *
543     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
544     *         (not wired up to be immediately usable).
545     *
546     * @throws IOException An IO error during parsing.
547     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
548     *                        errors in the SCXML document that may not be identified by the schema).
549     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
550     */
551    private static SCXML readInternal(final Configuration configuration, final URL scxmlURL, final String scxmlPath,
552                                      final InputStream scxmlStream, final Reader scxmlReader, final Source scxmlSource)
553            throws IOException, ModelException, XMLStreamException {
554
555        if (configuration.pathResolver == null) {
556            if (scxmlURL != null) {
557                configuration.pathResolver = new URLResolver(scxmlURL);
558            } else if (scxmlPath != null) {
559                configuration.pathResolver = new URLResolver(new URL(scxmlPath));
560            }
561        }
562
563        XMLStreamReader reader = getReader(configuration, scxmlURL, scxmlPath, scxmlStream, scxmlReader, scxmlSource);
564
565        return readDocument(reader, configuration);
566    }
567
568    /*
569     * Private utility functions for reading the SCXML document.
570     */
571    /**
572     * Read the SCXML document through the {@link XMLStreamReader}.
573     *
574     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
575     * @param configuration The {@link Configuration} to use while parsing.
576     *
577     * @return The parsed output, the Commons SCXML object model corresponding to the SCXML document
578     *         (not wired up to be immediately usable).
579     *
580     * @throws IOException An IO error during parsing.
581     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
582     *                        errors in the SCXML document that may not be identified by the schema).
583     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
584     */
585    private static SCXML readDocument(final XMLStreamReader reader, final Configuration configuration)
586            throws IOException, ModelException, XMLStreamException {
587
588        SCXML scxml = new SCXML();
589        while (reader.hasNext()) {
590            String name, nsURI;
591            switch (reader.next()) {
592                case XMLStreamConstants.START_ELEMENT:
593                    pushNamespaces(reader, configuration);
594                    nsURI = reader.getNamespaceURI();
595                    name = reader.getLocalName();
596                    if (XMLNS_SCXML.equals(nsURI)) {
597                        if (ELEM_SCXML.equals(name)) {
598                            readSCXML(reader, configuration, scxml);
599                        } else {
600                            reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
601                        }
602                    } else {
603                        reportIgnoredElement(reader, configuration, "DOCUMENT_ROOT", nsURI, name);
604                    }
605                    break;
606                case XMLStreamConstants.NAMESPACE:
607                    System.err.println(reader.getNamespaceCount());
608                    break;
609                default:
610            }
611        }
612        return scxml;
613    }
614
615    /**
616     * Read the contents of this &lt;scxml&gt; element.
617     *
618     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
619     * @param configuration The {@link Configuration} to use while parsing.
620     * @param scxml The root of the object model being parsed.
621     *
622     * @throws IOException An IO error during parsing.
623     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
624     *                        errors in the SCXML document that may not be identified by the schema).
625     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
626     */
627    private static void readSCXML(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml)
628            throws IOException, ModelException, XMLStreamException {
629
630        scxml.setDatamodelName(readAV(reader, ATTR_DATAMODEL));
631        scxml.setExmode(readAV(reader, ATTR_EXMODE));
632        scxml.setInitial(readAV(reader, ATTR_INITIAL));
633        scxml.setName(readAV(reader, ATTR_NAME));
634        scxml.setProfile(readAV(reader, ATTR_PROFILE));
635        scxml.setVersion(readRequiredAV(reader, ELEM_SCXML, ATTR_VERSION));
636        if (!SCXML_REQUIRED_VERSION.equals(scxml.getVersion())) {
637            throw new ModelException(new MessageFormat(ERR_INVALID_VERSION).format(new Object[] {scxml.getVersion()}));
638        }
639        readNamespaces(configuration, scxml);
640
641        boolean hasGlobalScript = false;
642
643        loop : while (reader.hasNext()) {
644            String name, nsURI;
645            switch (reader.next()) {
646                case XMLStreamConstants.START_ELEMENT:
647                    pushNamespaces(reader, configuration);
648                    nsURI = reader.getNamespaceURI();
649                    name = reader.getLocalName();
650                    if (XMLNS_SCXML.equals(nsURI)) {
651                        if (ELEM_STATE.equals(name)) {
652                            readState(reader, configuration, scxml, null);
653                        } else if (ELEM_PARALLEL.equals(name)) {
654                            readParallel(reader, configuration, scxml, null);
655                        } else if (ELEM_FINAL.equals(name)) {
656                            readFinal(reader, configuration, scxml, null);
657                        } else if (ELEM_DATAMODEL.equals(name)) {
658                            readDatamodel(reader, configuration, scxml, null);
659                        } else if (ELEM_SCRIPT.equals(name) && !hasGlobalScript) {
660                            readGlobalScript(reader, configuration, scxml);
661                            hasGlobalScript = true;
662                        } else {
663                            reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
664                        }
665                    } else {
666                        reportIgnoredElement(reader, configuration, ELEM_SCXML, nsURI, name);
667                    }
668                    break;
669                case XMLStreamConstants.END_ELEMENT:
670                    popNamespaces(reader, configuration);
671                    break loop;
672                default:
673            }
674        }
675    }
676
677    /**
678     * Read the contents of this &lt;state&gt; element.
679     *
680     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
681     * @param configuration The {@link Configuration} to use while parsing.
682     * @param scxml The root of the object model being parsed.
683     * @param parent The parent {@link TransitionalState} for this state (null for top level state).
684     *
685     * @throws IOException An IO error during parsing.
686     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
687     *                        errors in the SCXML document that may not be identified by the schema).
688     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
689     */
690    private static void readState(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
691                                  final TransitionalState parent)
692            throws IOException, ModelException, XMLStreamException {
693
694        State state = new State();
695        state.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_STATE));
696        String initial = readAV(reader, ATTR_INITIAL);
697        if (initial != null) {
698            state.setFirst(initial);
699        }
700        String src = readAV(reader, ATTR_SRC);
701        if (src != null) {
702            String source = src;
703            Configuration copy = new Configuration(configuration);
704            if (copy.parent == null) {
705                copy.parent = scxml;
706            }
707            if (configuration.pathResolver != null) {
708                source = configuration.pathResolver.resolvePath(src);
709                copy.pathResolver = configuration.pathResolver.getResolver(src);
710            }
711            readTransitionalStateSrc(copy, source, state);
712        }
713
714        if (parent == null) {
715            scxml.addChild(state);
716        } else if (parent instanceof State) {
717            ((State)parent).addChild(state);
718        }
719        else {
720            ((Parallel)parent).addChild(state);
721        }
722        scxml.addTarget(state);
723        if (configuration.parent != null) {
724            configuration.parent.addTarget(state);
725        }
726
727        loop : while (reader.hasNext()) {
728            String name, nsURI;
729            switch (reader.next()) {
730                case XMLStreamConstants.START_ELEMENT:
731                    pushNamespaces(reader, configuration);
732                    nsURI = reader.getNamespaceURI();
733                    name = reader.getLocalName();
734                    if (XMLNS_SCXML.equals(nsURI)) {
735                        if (ELEM_TRANSITION.equals(name)) {
736                            state.addTransition(readTransition(reader, configuration));
737                        } else if (ELEM_STATE.equals(name)) {
738                            readState(reader, configuration, scxml, state);
739                        } else if (ELEM_INITIAL.equals(name)) {
740                            readInitial(reader, configuration, state);
741                        } else if (ELEM_FINAL.equals(name)) {
742                            readFinal(reader, configuration, scxml, state);
743                        } else if (ELEM_ONENTRY.equals(name)) {
744                            readOnEntry(reader, configuration, state);
745                        } else if (ELEM_ONEXIT.equals(name)) {
746                            readOnExit(reader, configuration, state);
747                        } else if (ELEM_PARALLEL.equals(name)) {
748                            readParallel(reader, configuration, scxml, state);
749                        } else if (ELEM_DATAMODEL.equals(name)) {
750                            readDatamodel(reader, configuration, null, state);
751                        } else if (ELEM_INVOKE.equals(name)) {
752                            readInvoke(reader, configuration, state);
753                        } else if (ELEM_HISTORY.equals(name)) {
754                            readHistory(reader, configuration, scxml, state);
755                        } else {
756                            reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
757                        }
758                    } else {
759                        reportIgnoredElement(reader, configuration, ELEM_STATE, nsURI, name);
760                    }
761                    break;
762                case XMLStreamConstants.END_ELEMENT:
763                    popNamespaces(reader, configuration);
764                    break loop;
765                default:
766            }
767        }
768    }
769
770    /**
771     * Read the contents of this &lt;parallel&gt; element.
772     *
773     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
774     * @param configuration The {@link Configuration} to use while parsing.
775     * @param scxml The root of the object model being parsed.
776     * @param parent The parent {@link TransitionalState} for this parallel (null for top level state).
777     *
778     * @throws IOException An IO error during parsing.
779     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
780     *                        errors in the SCXML document that may not be identified by the schema).
781     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
782     */
783    private static void readParallel(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
784                                     final TransitionalState parent)
785            throws IOException, ModelException, XMLStreamException {
786
787        Parallel parallel = new Parallel();
788        parallel.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_PARALLEL));
789        String src = readAV(reader, ATTR_SRC);
790        if (src != null) {
791            String source = src;
792            Configuration copy = new Configuration(configuration);
793            if (copy.parent == null) {
794                copy.parent = scxml;
795            }
796            if (configuration.pathResolver != null) {
797                source = configuration.pathResolver.resolvePath(src);
798                copy.pathResolver = configuration.pathResolver.getResolver(src);
799            }
800            readTransitionalStateSrc(copy, source, parallel);
801        }
802
803        if (parent == null) {
804            scxml.addChild(parallel);
805        } else if (parent instanceof State) {
806            ((State)parent).addChild(parallel);
807        }
808        else {
809            ((Parallel)parent).addChild(parallel);
810        }
811        scxml.addTarget(parallel);
812        if (configuration.parent != null) {
813            configuration.parent.addTarget(parallel);
814        }
815
816        loop : while (reader.hasNext()) {
817            String name, nsURI;
818            switch (reader.next()) {
819                case XMLStreamConstants.START_ELEMENT:
820                    pushNamespaces(reader, configuration);
821                    nsURI = reader.getNamespaceURI();
822                    name = reader.getLocalName();
823                    if (XMLNS_SCXML.equals(nsURI)) {
824                        if (ELEM_TRANSITION.equals(name)) {
825                            parallel.addTransition(readTransition(reader, configuration));
826                        } else if (ELEM_STATE.equals(name)) {
827                            readState(reader, configuration, scxml, parallel);
828                        } else if (ELEM_PARALLEL.equals(name)) {
829                            readParallel(reader, configuration, scxml, parallel);
830                        } else if (ELEM_ONENTRY.equals(name)) {
831                            readOnEntry(reader, configuration, parallel);
832                        } else if (ELEM_ONEXIT.equals(name)) {
833                            readOnExit(reader, configuration, parallel);
834                        } else if (ELEM_DATAMODEL.equals(name)) {
835                            readDatamodel(reader, configuration, null, parallel);
836                        } else if (ELEM_INVOKE.equals(name)) {
837                            readInvoke(reader, configuration, parallel);
838                        } else if (ELEM_HISTORY.equals(name)) {
839                            readHistory(reader, configuration, scxml, parallel);
840                        } else {
841                            reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
842                        }
843                    } else {
844                        reportIgnoredElement(reader, configuration, ELEM_PARALLEL, nsURI, name);
845                    }
846                    break;
847                case XMLStreamConstants.END_ELEMENT:
848                    popNamespaces(reader, configuration);
849                    break loop;
850                default:
851            }
852        }
853    }
854
855    /**
856     * Read the contents of this &lt;final&gt; element.
857     *
858     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
859     * @param configuration The {@link Configuration} to use while parsing.
860     * @param scxml The root of the object model being parsed.
861     * @param parent The parent {@link State} for this final (null for top level state).
862     *
863     * @throws IOException An IO error during parsing.
864     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
865     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
866     *                        errors in the SCXML document that may not be identified by the schema).
867     */
868    private static void readFinal(final XMLStreamReader reader, final Configuration configuration, final SCXML scxml,
869                                  final State parent)
870            throws XMLStreamException, ModelException, IOException {
871
872        Final end = new Final();
873        end.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_FINAL));
874
875        if (parent == null) {
876            scxml.addChild(end);
877        } else {
878            parent.addChild(end);
879        }
880
881        scxml.addTarget(end);
882        if (configuration.parent != null) {
883            configuration.parent.addTarget(end);
884        }
885
886        loop : while (reader.hasNext()) {
887            String name, nsURI;
888            switch (reader.next()) {
889                case XMLStreamConstants.START_ELEMENT:
890                    pushNamespaces(reader, configuration);
891                    nsURI = reader.getNamespaceURI();
892                    name = reader.getLocalName();
893                    if (XMLNS_SCXML.equals(nsURI)) {
894                        if (ELEM_ONENTRY.equals(name)) {
895                            readOnEntry(reader, configuration, end);
896                        } else if (ELEM_ONEXIT.equals(name)) {
897                            readOnExit(reader, configuration, end);
898                        } else {
899                            reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
900                        }
901                    } else {
902                        reportIgnoredElement(reader, configuration, ELEM_FINAL, nsURI, name);
903                    }
904                    break;
905                case XMLStreamConstants.END_ELEMENT:
906                    popNamespaces(reader, configuration);
907                    break loop;
908                default:
909            }
910        }
911    }
912
913    /**
914     * Parse the contents of the SCXML document that this "src" attribute value of a &lt;state&gt; or &lt;parallel&gt;
915     * element points to. Without a URL fragment, the entire state machine is imported as contents of the
916     * &lt;state&gt; or &lt;parallel&gt;. If a URL fragment is present, the fragment must specify the id of the
917     * corresponding &lt;state&gt; or &lt;parallel&gt; to import.
918     *
919     * @param configuration The {@link Configuration} to use while parsing.
920     * @param src The "src" attribute value.
921     * @param ts The parent {@link TransitionalState} that specifies this "src" attribute.
922     *
923     * @throws IOException An IO error during parsing.
924     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
925     *                        errors in the SCXML document that may not be identified by the schema).
926     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
927     */
928    private static void readTransitionalStateSrc(final Configuration configuration, final String src,
929                                                 final TransitionalState ts)
930            throws IOException, ModelException, XMLStreamException {
931
932        // Check for URI fragment
933        String[] fragments = src.split("#", 2);
934        String location = fragments[0];
935        String fragment = null;
936        if (fragments.length > 1) {
937            fragment = fragments[1];
938        }
939
940        // Parse external document
941        SCXML externalSCXML;
942        try {
943            externalSCXML = SCXMLReader.readInternal(configuration, new URL(location), null, null, null, null);
944        } catch (Exception e) {
945            MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC);
946            String errMsg = msgFormat.format(new Object[] {src});
947            throw new ModelException(errMsg + " : " + e.getMessage(), e);
948        }
949
950        // Pull in the parts of the external document as needed
951        if (fragment == null) {
952            // All targets pulled in since its not a src fragment
953            if (ts instanceof State) {
954                State s = (State) ts;
955                Initial ini = new Initial();
956                SimpleTransition t = new SimpleTransition();
957                t.setNext(externalSCXML.getInitial());
958                ini.setTransition(t);
959                s.setInitial(ini);
960                for (EnterableState child : externalSCXML.getChildren()) {
961                    s.addChild(child);
962                }
963                s.setDatamodel(externalSCXML.getDatamodel());
964            } else if (ts instanceof Parallel) {
965                // TODO src attribute for <parallel>
966            }
967        } else {
968            // Need to pull in only descendent targets
969            Object source = externalSCXML.getTargets().get(fragment);
970            if (source == null) {
971                MessageFormat msgFormat = new MessageFormat(ERR_STATE_SRC_FRAGMENT);
972                String errMsg = msgFormat.format(new Object[] {src});
973                throw new ModelException(errMsg);
974            }
975            if (source instanceof State && ts instanceof State) {
976                State s = (State) ts;
977                State include = (State) source;
978                for (OnEntry onentry : include.getOnEntries()) {
979                    s.addOnEntry(onentry);
980                }
981                for (OnExit onexit : include.getOnExits()) {
982                    s.addOnExit(onexit);
983                }
984                s.setDatamodel(include.getDatamodel());
985                List<History> histories = include.getHistory();
986                for (History h : histories) {
987                    s.addHistory(h);
988                    configuration.parent.addTarget(h);
989                }
990                for (EnterableState child : include.getChildren()) {
991                    s.addChild(child);
992                    configuration.parent.addTarget(child);
993                    readInExternalTargets(configuration.parent, child);
994                }
995                for (Invoke invoke : include.getInvokes()) {
996                    s.addInvoke(invoke);
997                }
998                if (include.getInitial() != null) {
999                    s.setInitial(include.getInitial());
1000                }
1001                List<Transition> transitions = include.getTransitionsList();
1002                for (Transition t : transitions) {
1003                    s.addTransition(t);
1004                }
1005            } else if (ts instanceof Parallel && source instanceof Parallel) {
1006                // TODO src attribute for <parallel>
1007            } else {
1008                MessageFormat msgFormat =
1009                        new MessageFormat(ERR_STATE_SRC_FRAGMENT_TARGET);
1010                String errMsg = msgFormat.format(new Object[] {src});
1011                throw new ModelException(errMsg);
1012            }
1013        }
1014    }
1015
1016    /**
1017     * Add all the nested targets from given target to given parent state machine.
1018     *
1019     * @param parent The state machine
1020     * @param es The target to import
1021     */
1022    private static void readInExternalTargets(final SCXML parent, final EnterableState es) {
1023        if (es instanceof TransitionalState) {
1024            for (History h : ((TransitionalState)es).getHistory()) {
1025                parent.addTarget(h);
1026            }
1027            for (EnterableState child : ((TransitionalState) es).getChildren()) {
1028                parent.addTarget(child);
1029                readInExternalTargets(parent, child);
1030            }
1031        }
1032    }
1033
1034    /**
1035     * Read the contents of this &lt;datamodel&gt; element.
1036     *
1037     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1038     * @param configuration The {@link Configuration} to use while parsing.
1039     * @param scxml The root of the object model being parsed.
1040     * @param parent The parent {@link TransitionalState} for this datamodel (null for top level).
1041     *
1042     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1043     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1044     *                        errors in the SCXML document that may not be identified by the schema).
1045     */
1046    private static void readDatamodel(final XMLStreamReader reader, final Configuration configuration,
1047                                      final SCXML scxml, final TransitionalState parent)
1048            throws XMLStreamException, ModelException {
1049
1050        Datamodel dm = new Datamodel();
1051
1052        loop : while (reader.hasNext()) {
1053            String name, nsURI;
1054            switch (reader.next()) {
1055                case XMLStreamConstants.START_ELEMENT:
1056                    pushNamespaces(reader, configuration);
1057                    nsURI = reader.getNamespaceURI();
1058                    name = reader.getLocalName();
1059                    if (XMLNS_SCXML.equals(nsURI)) {
1060                        if (ELEM_DATA.equals(name)) {
1061                            readData(reader, configuration, dm);
1062                        } else {
1063                            reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
1064                        }
1065                    } else {
1066                        reportIgnoredElement(reader, configuration, ELEM_DATAMODEL, nsURI, name);
1067                    }
1068                    break;
1069                case XMLStreamConstants.END_ELEMENT:
1070                    popNamespaces(reader, configuration);
1071                    break loop;
1072                default:
1073            }
1074        }
1075
1076        if (parent == null) {
1077            scxml.setDatamodel(dm);
1078        } else {
1079            parent.setDatamodel(dm);
1080        }
1081    }
1082
1083    /**
1084     * Read the contents of this &lt;data&gt; element.
1085     *
1086     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1087     * @param configuration The {@link Configuration} to use while parsing.
1088     * @param dm The parent {@link Datamodel} for this data.
1089     *
1090     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1091     */
1092    private static void readData(final XMLStreamReader reader, final Configuration configuration, final Datamodel dm)
1093            throws XMLStreamException, ModelException {
1094
1095        Data datum = new Data();
1096        datum.setId(readRequiredAV(reader, ELEM_DATA, ATTR_ID));
1097        datum.setExpr(readAV(reader, ATTR_EXPR));
1098        readNamespaces(configuration, datum);
1099        datum.setNode(readNode(reader, configuration, XMLNS_SCXML, ELEM_DATA, new String[]{"id"}));
1100        dm.addData(datum);
1101    }
1102
1103    /**
1104     * Read the contents of this &lt;invoke&gt; element.
1105     *
1106     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1107     * @param configuration The {@link Configuration} to use while parsing.
1108     * @param parent The parent {@link TransitionalState} for this invoke.
1109     *
1110     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1111     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1112     *                        errors in the SCXML document that may not be identified by the schema).
1113     */
1114    private static void readInvoke(final XMLStreamReader reader, final Configuration configuration,
1115                                   final TransitionalState parent)
1116            throws XMLStreamException, ModelException {
1117
1118        Invoke invoke = new Invoke();
1119        invoke.setId(readAV(reader, ATTR_ID));
1120        invoke.setSrc(readAV(reader, ATTR_SRC));
1121        invoke.setSrcexpr(readAV(reader, ATTR_SRCEXPR));
1122        invoke.setType(readAV(reader, ATTR_TYPE));
1123        invoke.setAutoForward(readBooleanAV(reader, ELEM_INVOKE, ATTR_AUTOFORWARD));
1124        invoke.setPathResolver(configuration.pathResolver);
1125        readNamespaces(configuration, invoke);
1126
1127        loop : while (reader.hasNext()) {
1128            String name, nsURI;
1129            switch (reader.next()) {
1130                case XMLStreamConstants.START_ELEMENT:
1131                    pushNamespaces(reader, configuration);
1132                    nsURI = reader.getNamespaceURI();
1133                    name = reader.getLocalName();
1134                    if (XMLNS_SCXML.equals(nsURI)) {
1135                        if (ELEM_PARAM.equals(name)) {
1136                            readParam(reader, configuration, invoke);
1137                        } else if (ELEM_FINALIZE.equals(name)) {
1138                            readFinalize(reader, configuration, parent, invoke);
1139                        } else if (ELEM_CONTENT.equals(name)) {
1140                            readContent(reader, configuration, invoke);
1141                        } else {
1142                            reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
1143                        }
1144                    } else {
1145                        reportIgnoredElement(reader, configuration, ELEM_INVOKE, nsURI, name);
1146                    }
1147                    break;
1148                case XMLStreamConstants.END_ELEMENT:
1149                    popNamespaces(reader, configuration);
1150                    break loop;
1151                default:
1152            }
1153        }
1154
1155        parent.addInvoke(invoke);
1156    }
1157
1158    /**
1159     * Read the contents of this &lt;param&gt; element.
1160     *
1161     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1162     * @param configuration The {@link Configuration} to use while parsing.
1163     * @param parent The parent {@link org.apache.commons.scxml2.model.ParamsContainer} for this param.
1164     *
1165     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1166     */
1167    private static void readParam(final XMLStreamReader reader, final Configuration configuration,
1168                                  final ParamsContainer parent)
1169            throws XMLStreamException, ModelException {
1170
1171        Param param = new Param();
1172        param.setName(readRequiredAV(reader, ELEM_PARAM, ATTR_NAME));
1173        String location = readAV(reader, ATTR_LOCATION);
1174        String expr = readAV(reader, ATTR_EXPR);
1175        if (expr != null) {
1176            if (location != null) {
1177                reportConflictingAttribute(reader, configuration, ELEM_PARAM, ATTR_LOCATION, ATTR_EXPR);
1178            }
1179            else {
1180                param.setExpr(expr);
1181            }
1182        }
1183        else if (location == null) {
1184            // force error missing required location or expr: use location attr for this
1185            param.setLocation(readRequiredAV(reader, ELEM_PARAM, ATTR_LOCATION));
1186        }
1187        else {
1188            param.setLocation(location);
1189        }
1190        readNamespaces(configuration, param);
1191        parent.getParams().add(param);
1192        skipToEndElement(reader);
1193    }
1194
1195    /**
1196     * Read the contents of this &lt;finalize&gt; element.
1197     *
1198     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1199     * @param configuration The {@link Configuration} to use while parsing.
1200     * @param state The {@link TransitionalState} which contains the parent {@link Invoke}.
1201     * @param invoke The parent {@link Invoke} for this finalize.
1202     *
1203     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1204     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1205     *                        errors in the SCXML document that may not be identified by the schema).
1206     */
1207    private static void readFinalize(final XMLStreamReader reader, final Configuration configuration,
1208                                     final TransitionalState state, final Invoke invoke)
1209            throws XMLStreamException, ModelException {
1210
1211        Finalize finalize = new Finalize();
1212        readExecutableContext(reader, configuration, finalize, null);
1213        invoke.setFinalize(finalize);
1214        finalize.setParent(state);
1215    }
1216
1217    /**
1218     * Read the contents of this &lt;content&gt; element.
1219     *
1220     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1221     * @param configuration The {@link Configuration} to use while parsing.
1222     * @param contentContainer The {@link ContentContainer} for this content.
1223     *
1224     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1225     */
1226    private static void readContent(final XMLStreamReader reader, final Configuration configuration,
1227                                    final ContentContainer contentContainer)
1228            throws XMLStreamException {
1229
1230        Content content = new Content();
1231        content.setExpr(readAV(reader, ATTR_EXPR));
1232        if (content.getExpr() != null) {
1233            skipToEndElement(reader);
1234        }
1235        else {
1236            Node body = readNode(reader, configuration, XMLNS_SCXML, ELEM_CONTENT, new String[]{});
1237            if (body.hasChildNodes()) {
1238                NodeList children = body.getChildNodes();
1239                if (children.getLength() == 1 && children.item(0).getNodeType() == Node.TEXT_NODE) {
1240                    content.setBody(children.item(0).getNodeValue());
1241                }
1242                else {
1243                    content.setBody(body);
1244                }
1245            }
1246        }
1247        contentContainer.setContent(content);
1248    }
1249
1250    /**
1251     * Read the contents of this &lt;initial&gt; element.
1252     *
1253     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1254     * @param configuration The {@link Configuration} to use while parsing.
1255     * @param state The parent composite {@link State} for this initial.
1256     *
1257     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1258     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1259     *                        errors in the SCXML document that may not be identified by the schema).
1260     */
1261    private static void readInitial(final XMLStreamReader reader, final Configuration configuration,
1262                                    final State state)
1263            throws XMLStreamException, ModelException {
1264
1265        Initial initial = new Initial();
1266
1267        loop : while (reader.hasNext()) {
1268            String name, nsURI;
1269            switch (reader.next()) {
1270                case XMLStreamConstants.START_ELEMENT:
1271                    pushNamespaces(reader, configuration);
1272                    nsURI = reader.getNamespaceURI();
1273                    name = reader.getLocalName();
1274                    if (XMLNS_SCXML.equals(nsURI)) {
1275                        if (ELEM_TRANSITION.equals(name)) {
1276                            initial.setTransition(readSimpleTransition(reader, configuration));
1277                        } else {
1278                            reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
1279                        }
1280                    } else {
1281                        reportIgnoredElement(reader, configuration, ELEM_INITIAL, nsURI, name);
1282                    }
1283                    break;
1284                case XMLStreamConstants.END_ELEMENT:
1285                    popNamespaces(reader, configuration);
1286                    break loop;
1287                default:
1288            }
1289        }
1290
1291        state.setInitial(initial);
1292    }
1293
1294    /**
1295     * Read the contents of this &lt;history&gt; element.
1296     *
1297     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1298     * @param configuration The {@link Configuration} to use while parsing.
1299     * @param scxml The root of the object model being parsed.
1300     * @param ts The parent {@link org.apache.commons.scxml2.model.TransitionalState} for this history.
1301     *
1302     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1303     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1304     *                        errors in the SCXML document that may not be identified by the schema).
1305     */
1306    private static void readHistory(final XMLStreamReader reader, final Configuration configuration,
1307                                    final SCXML scxml, final TransitionalState ts)
1308            throws XMLStreamException, ModelException {
1309
1310        History history = new History();
1311        history.setId(readOrGeneratedTransitionTargetId(reader, scxml, ELEM_HISTORY));
1312        history.setType(readAV(reader, ATTR_TYPE));
1313
1314        ts.addHistory(history);
1315        scxml.addTarget(history);
1316
1317        loop : while (reader.hasNext()) {
1318            String name, nsURI;
1319            switch (reader.next()) {
1320                case XMLStreamConstants.START_ELEMENT:
1321                    pushNamespaces(reader, configuration);
1322                    nsURI = reader.getNamespaceURI();
1323                    name = reader.getLocalName();
1324                    if (XMLNS_SCXML.equals(nsURI)) {
1325                        if (ELEM_TRANSITION.equals(name)) {
1326                            history.setTransition(readTransition(reader, configuration));
1327                        } else {
1328                            reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
1329                        }
1330                    } else {
1331                        reportIgnoredElement(reader, configuration, ELEM_HISTORY, nsURI, name);
1332                    }
1333                    break;
1334                case XMLStreamConstants.END_ELEMENT:
1335                    popNamespaces(reader, configuration);
1336                    break loop;
1337                default:
1338            }
1339        }
1340    }
1341
1342    /**
1343     * Read the contents of this &lt;onentry&gt; element.
1344     *
1345     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1346     * @param configuration The {@link Configuration} to use while parsing.
1347     * @param es The parent {@link EnterableState} for this onentry.
1348     *
1349     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1350     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1351     *                        errors in the SCXML document that may not be identified by the schema).
1352     */
1353    private static void readOnEntry(final XMLStreamReader reader, final Configuration configuration,
1354                                    final EnterableState es)
1355            throws XMLStreamException, ModelException {
1356
1357        OnEntry onentry = new OnEntry();
1358        onentry.setRaiseEvent(readBooleanAV(reader, ELEM_ONENTRY, ATTR_EVENT));
1359        readExecutableContext(reader, configuration, onentry, null);
1360        es.addOnEntry(onentry);
1361    }
1362
1363    /**
1364     * Read the contents of this &lt;onexit&gt; element.
1365     *
1366     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1367     * @param configuration The {@link Configuration} to use while parsing.
1368     * @param es The parent {@link EnterableState} for this onexit.
1369     *
1370     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1371     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1372     *                        errors in the SCXML document that may not be identified by the schema).
1373     */
1374    private static void readOnExit(final XMLStreamReader reader, final Configuration configuration,
1375                                   final EnterableState es)
1376            throws XMLStreamException, ModelException {
1377
1378        OnExit onexit = new OnExit();
1379        onexit.setRaiseEvent(readBooleanAV(reader, ELEM_ONEXIT, ATTR_EVENT));
1380        readExecutableContext(reader, configuration, onexit, null);
1381        es.addOnExit(onexit);
1382    }
1383
1384    /**
1385     * Read the contents of this simple &lt;transition&gt; element.
1386     *
1387     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1388     * @param configuration The {@link Configuration} to use while parsing.
1389     *
1390     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1391     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1392     *                        errors in the SCXML document that may not be identified by the schema).
1393     */
1394    private static SimpleTransition readSimpleTransition(final XMLStreamReader reader, final Configuration configuration)
1395            throws XMLStreamException, ModelException {
1396
1397        SimpleTransition transition = new SimpleTransition();
1398        transition.setNext(readAV(reader, ATTR_TARGET));
1399        String type = readAV(reader, ATTR_TYPE);
1400        if (type != null) {
1401            try {
1402                transition.setType(TransitionType.valueOf(type));
1403            }
1404            catch (IllegalArgumentException e) {
1405                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
1406                String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
1407                throw new ModelException(errMsg);
1408            }
1409        }
1410
1411        readNamespaces(configuration, transition);
1412        readExecutableContext(reader, configuration, transition, null);
1413
1414        return transition;
1415    }
1416
1417    /**
1418     * Read the contents of this &lt;transition&gt; element.
1419     *
1420     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1421     * @param configuration The {@link Configuration} to use while parsing.
1422     *
1423     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1424     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1425     *                        errors in the SCXML document that may not be identified by the schema).
1426     */
1427    private static Transition readTransition(final XMLStreamReader reader, final Configuration configuration)
1428            throws XMLStreamException, ModelException {
1429
1430        Transition transition = new Transition();
1431        transition.setCond(readAV(reader, ATTR_COND));
1432        transition.setEvent(readAV(reader, ATTR_EVENT));
1433        transition.setNext(readAV(reader, ATTR_TARGET));
1434        String type = readAV(reader, ATTR_TYPE);
1435        if (type != null) {
1436            try {
1437                transition.setType(TransitionType.valueOf(type));
1438            }
1439            catch (IllegalArgumentException e) {
1440                MessageFormat msgFormat = new MessageFormat(ERR_UNSUPPORTED_TRANSITION_TYPE);
1441                String errMsg = msgFormat.format(new Object[] {type, reader.getLocation()});
1442                throw new ModelException(errMsg);
1443            }
1444        }
1445
1446        readNamespaces(configuration, transition);
1447        readExecutableContext(reader, configuration, transition, null);
1448
1449        return transition;
1450    }
1451
1452    /**
1453     * Read this set of executable content elements.
1454     *
1455     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1456     * @param configuration The {@link Configuration} to use while parsing.
1457     * @param executable The parent {@link Executable} to which this content belongs.
1458     * @param parent The optional parent {@link ActionsContainer} if this is child content of an ActionsContainer action.
1459     *
1460     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1461     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1462     *                        errors in the SCXML document that may not be identified by the schema).
1463     */
1464    private static void readExecutableContext(final XMLStreamReader reader, final Configuration configuration,
1465                                              final Executable executable, final ActionsContainer parent)
1466            throws XMLStreamException, ModelException {
1467
1468        String end = "";
1469        if (parent != null) {
1470            end = parent.getContainerElementName();
1471        } else if (executable instanceof SimpleTransition) {
1472            end = ELEM_TRANSITION;
1473        } else if (executable instanceof OnEntry) {
1474            end = ELEM_ONENTRY;
1475        } else if (executable instanceof OnExit) {
1476            end = ELEM_ONEXIT;
1477        } else if (executable instanceof Finalize) {
1478            end = ELEM_FINALIZE;
1479        }
1480
1481        loop : while (reader.hasNext()) {
1482            String name, nsURI;
1483            switch (reader.next()) {
1484                case XMLStreamConstants.START_ELEMENT:
1485                    pushNamespaces(reader, configuration);
1486                    nsURI = reader.getNamespaceURI();
1487                    name = reader.getLocalName();
1488                    if (XMLNS_SCXML.equals(nsURI)) {
1489                        if (ELEM_RAISE.equals(name)) {
1490                            readRaise(reader, configuration, executable, parent);
1491                        } else if (ELEM_FOREACH.equals(name)) {
1492                            readForeach(reader, configuration, executable, parent);
1493                        } else if (ELEM_IF.equals(name)) {
1494                            readIf(reader, configuration, executable, parent);
1495                        } else if (ELEM_LOG.equals(name)) {
1496                            readLog(reader, configuration, executable, parent);
1497                        } else if (ELEM_ASSIGN.equals(name)) {
1498                            readAssign(reader, configuration, executable, parent);
1499                        } else if (ELEM_SEND.equals(name)) {
1500                            readSend(reader, configuration, executable, parent);
1501                        } else if (ELEM_CANCEL.equals(name)) {
1502                            readCancel(reader, configuration, executable, parent);
1503                        } else if (ELEM_SCRIPT.equals(name)) {
1504                            readScript(reader, configuration, executable, parent);
1505                        } else if (ELEM_IF.equals(end) && ELEM_ELSEIF.equals(name)) {
1506                            readElseIf(reader, configuration, executable, (If) parent);
1507                        } else if (ELEM_IF.equals(end) && ELEM_ELSE.equals(name)) {
1508                            readElse(reader, configuration, executable, (If)parent);
1509                        } else {
1510                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1511                        }
1512                    } else if (XMLNS_COMMONS_SCXML.equals(nsURI)) {
1513                        if (ELEM_VAR.equals(name)) {
1514                            readVar(reader, configuration, executable, parent);
1515                        } else {
1516                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1517                        }
1518                    } else { // custom action
1519                        CustomAction customAction = null;
1520                        if (!configuration.customActions.isEmpty()) {
1521                            for (CustomAction ca : configuration.customActions) {
1522                                if (ca.getNamespaceURI().equals(nsURI) && ca.getLocalName().equals(name)) {
1523                                    customAction = ca;
1524                                }
1525                            }
1526                        }
1527                        if (customAction != null) {
1528                            readCustomAction(reader, configuration, customAction, executable, parent);
1529                        } else {
1530                            reportIgnoredElement(reader, configuration, end, nsURI, name);
1531                        }
1532                    }
1533                    break;
1534                case XMLStreamConstants.END_ELEMENT:
1535                    popNamespaces(reader, configuration);
1536                    break loop;
1537                default:
1538            }
1539        }
1540    }
1541
1542    /**
1543     * Read the contents of this &lt;raise&gt; element.
1544     *
1545     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1546     * @param configuration The {@link Configuration} to use while parsing.
1547     * @param executable The parent {@link Executable} for this action.
1548     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1549     *
1550     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1551     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1552     *                        errors in the SCXML document that may not be identified by the schema).
1553     */
1554    private static void readRaise(final XMLStreamReader reader, final Configuration configuration,
1555                                  final Executable executable, final ActionsContainer parent)
1556            throws XMLStreamException, ModelException {
1557
1558        if (executable instanceof Finalize) {
1559            // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
1560            // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
1561            // In particular, the <send> and <raise> elements MUST NOT occur.
1562            reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_RAISE);
1563        }
1564        else {
1565            Raise raise = new Raise();
1566            raise.setEvent(readAV(reader, ATTR_EVENT));
1567            readNamespaces(configuration, raise);
1568            raise.setParent(executable);
1569            if (parent != null) {
1570                parent.addAction(raise);
1571            } else {
1572                executable.addAction(raise);
1573            }
1574            skipToEndElement(reader);
1575        }
1576    }
1577
1578    /**
1579     * Read the contents of this &lt;if&gt; element.
1580     *
1581     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1582     * @param configuration The {@link Configuration} to use while parsing.
1583     * @param executable The parent {@link Executable} for this action.
1584     * @param parent The optional parent {@link ActionsContainer} if this &lt;if&gt; is a child of one.
1585     *
1586     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1587     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1588     *                        errors in the SCXML document that may not be identified by the schema).
1589     */
1590    private static void readIf(final XMLStreamReader reader, final Configuration configuration,
1591                               final Executable executable, final ActionsContainer parent)
1592            throws XMLStreamException, ModelException {
1593
1594        If iff = new If();
1595        iff.setCond(readRequiredAV(reader, ELEM_IF, ATTR_COND));
1596        readNamespaces(configuration, iff);
1597        iff.setParent(executable);
1598        if (parent != null) {
1599            parent.addAction(iff);
1600        } else {
1601            executable.addAction(iff);
1602        }
1603        readExecutableContext(reader, configuration, executable, iff);
1604    }
1605
1606    /**
1607     * Read the contents of this &lt;elseif&gt; element.
1608     *
1609     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1610     * @param configuration The {@link Configuration} to use while parsing.
1611     * @param executable The parent {@link Executable} for this action.
1612     * @param iff The parent {@link If} for this &lt;elseif&gt;.
1613     *
1614     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1615     */
1616    private static void readElseIf(final XMLStreamReader reader, final Configuration configuration,
1617                                   final Executable executable, final If iff)
1618            throws XMLStreamException, ModelException {
1619
1620        ElseIf elseif = new ElseIf();
1621        elseif.setCond(readRequiredAV(reader, ELEM_ELSEIF, ATTR_COND));
1622        readNamespaces(configuration, elseif);
1623        elseif.setParent(executable);
1624        iff.addAction(elseif);
1625        skipToEndElement(reader);
1626    }
1627
1628    /**
1629     * Read the contents of this &lt;else&gt; element.
1630     *
1631     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1632     * @param configuration The {@link Configuration} to use while parsing.
1633     * @param executable The parent {@link Executable} for this action.
1634     * @param iff The parent {@link If} for this &lt;else&gt;.
1635     *
1636     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1637     */
1638    private static void readElse(final XMLStreamReader reader, final Configuration configuration,
1639                                 final Executable executable, final If iff)
1640            throws XMLStreamException {
1641
1642        Else els = new Else();
1643        readNamespaces(configuration, els);
1644        els.setParent(executable);
1645        iff.addAction(els);
1646        skipToEndElement(reader);
1647    }
1648
1649    /**
1650     * Read the contents of this &lt;foreach&gt; element.
1651     *
1652     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1653     * @param configuration The {@link Configuration} to use while parsing.
1654     * @param executable The parent {@link Executable} for this action.
1655     * @param parent The optional parent {@link ActionsContainer} if this &lt;foreach&gt; is a child of one.
1656     *
1657     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1658     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1659     *                        errors in the SCXML document that may not be identified by the schema).
1660     */
1661    private static void readForeach(final XMLStreamReader reader, final Configuration configuration,
1662                                    final Executable executable, final ActionsContainer parent)
1663            throws XMLStreamException, ModelException {
1664
1665        Foreach fe = new Foreach();
1666        fe.setArray(readRequiredAV(reader, ELEM_FOREACH, ATTR_ARRAY));
1667        fe.setItem(readRequiredAV(reader, ELEM_FOREACH, ATTR_ITEM));
1668        fe.setIndex(readAV(reader, ATTR_INDEX));
1669        readNamespaces(configuration, fe);
1670        fe.setParent(executable);
1671        if (parent != null) {
1672            parent.addAction(fe);
1673        } else {
1674            executable.addAction(fe);
1675        }
1676        readExecutableContext(reader, configuration, executable, fe);
1677    }
1678
1679    /**
1680     * Read the contents of this &lt;log&gt; element.
1681     *
1682     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1683     * @param configuration The {@link Configuration} to use while parsing.
1684     * @param executable The parent {@link Executable} for this action.
1685     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1686     *
1687     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1688     */
1689    private static void readLog(final XMLStreamReader reader, final Configuration configuration,
1690                                final Executable executable, final ActionsContainer parent)
1691            throws XMLStreamException {
1692
1693        Log log = new Log();
1694        log.setExpr(readAV(reader, ATTR_EXPR));
1695        log.setLabel(readAV(reader, ATTR_LABEL));
1696        readNamespaces(configuration, log);
1697        log.setParent(executable);
1698        if (parent != null) {
1699            parent.addAction(log);
1700        } else {
1701            executable.addAction(log);
1702        }
1703        skipToEndElement(reader);
1704    }
1705
1706    /**
1707     * Read the contents of this &lt;assign&gt; element.
1708     *
1709     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1710     * @param configuration The {@link Configuration} to use while parsing.
1711     * @param executable The parent {@link Executable} for this action.
1712     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1713     *
1714     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1715     */
1716    private static void readAssign(final XMLStreamReader reader, final Configuration configuration,
1717                                   final Executable executable, final ActionsContainer parent)
1718            throws XMLStreamException, ModelException {
1719
1720        Assign assign = new Assign();
1721        assign.setExpr(readAV(reader, ATTR_EXPR));
1722        assign.setLocation(readRequiredAV(reader, ELEM_ASSIGN, ATTR_LOCATION));
1723        String attrValue = readAV(reader, ATTR_TYPE);
1724        if (attrValue != null) {
1725            assign.setType(Evaluator.AssignType.fromValue(attrValue));
1726            if (assign.getType() == null) {
1727                reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_TYPE, attrValue);
1728            }
1729        }
1730        attrValue = readAV(reader, ATTR_ATTR);
1731        if (attrValue != null) {
1732            if (Evaluator.AssignType.ADD_ATTRIBUTE.equals(assign.getType())) {
1733                assign.setAttr(attrValue);
1734            }
1735            else {
1736                reportIgnoredAttribute(reader, configuration, ELEM_ASSIGN, ATTR_ATTR, attrValue);
1737            }
1738        }
1739        assign.setSrc(readAV(reader, ATTR_SRC));
1740        assign.setPathResolver(configuration.pathResolver);
1741        readNamespaces(configuration, assign);
1742        assign.setParent(executable);
1743        if (parent != null) {
1744            parent.addAction(assign);
1745        } else {
1746            executable.addAction(assign);
1747        }
1748        skipToEndElement(reader);
1749    }
1750
1751    /**
1752     * Read the contents of this &lt;send&gt; element.
1753     *
1754     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1755     * @param configuration The {@link Configuration} to use while parsing.
1756     * @param executable The parent {@link Executable} for this action.
1757     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1758     *
1759     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1760     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
1761     *                        errors in the SCXML document that may not be identified by the schema).
1762     */
1763    private static void readSend(final XMLStreamReader reader, final Configuration configuration,
1764                                 final Executable executable, final ActionsContainer parent)
1765            throws XMLStreamException, ModelException {
1766
1767        if (executable instanceof Finalize) {
1768            // http://www.w3.org/TR/2013/WD-scxml-20130801/#finalize
1769            // [...] the executable content inside <finalize> MUST NOT raise events or invoke external actions.
1770            // In particular, the <send> and <raise> elements MUST NOT occur.
1771            reportIgnoredElement(reader, configuration, ELEM_FINALIZE, XMLNS_SCXML, ELEM_SEND);
1772            return;
1773        }
1774
1775        Send send = new Send();
1776        send.setId(readAV(reader, ATTR_ID));
1777        String attrValue = readAV(reader, ATTR_IDLOCATION);
1778        if (attrValue != null) {
1779            if (send.getId() != null) {
1780                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_ID, ATTR_IDLOCATION);
1781            }
1782            else {
1783                send.setIdlocation(attrValue);
1784            }
1785        }
1786        send.setDelay(readAV(reader, ATTR_DELAY));
1787        attrValue = readAV(reader, ATTR_DELAYEXPR);
1788        if (attrValue != null) {
1789            if (send.getDelay() != null) {
1790                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_DELAY, ATTR_DELAYEXPR);
1791            }
1792            else {
1793                send.setDelayexpr(attrValue);
1794            }
1795        }
1796        send.setEvent(readAV(reader, ATTR_EVENT));
1797        attrValue = readAV(reader, ATTR_EVENTEXPR);
1798        if (attrValue != null) {
1799            if (send.getEvent() != null) {
1800                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_EVENT, ATTR_EVENTEXPR);
1801            }
1802            else {
1803                send.setEventexpr(attrValue);
1804            }
1805        }
1806        send.setHints(readAV(reader, ATTR_HINTS));
1807        send.setNamelist(readAV(reader, ATTR_NAMELIST));
1808        send.setTarget(readAV(reader, ATTR_TARGET));
1809        attrValue = readAV(reader, ATTR_TARGETEXPR);
1810        if (attrValue != null) {
1811            if (send.getTarget() != null) {
1812                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TARGET, ATTR_TARGETEXPR);
1813            }
1814            else {
1815                send.setTargetexpr(attrValue);
1816            }
1817        }
1818        send.setType(readAV(reader, ATTR_TYPE));
1819        attrValue = readAV(reader, ATTR_TYPEEXPR);
1820        if (attrValue != null) {
1821            if (send.getType() != null) {
1822                reportConflictingAttribute(reader, configuration, ELEM_SEND, ATTR_TYPE, ATTR_TYPEEXPR);
1823            }
1824            else {
1825                send.setTypeexpr(attrValue);
1826            }
1827        }
1828        readNamespaces(configuration, send);
1829
1830        loop : while (reader.hasNext()) {
1831            String name, nsURI;
1832            switch (reader.next()) {
1833                case XMLStreamConstants.START_ELEMENT:
1834                    pushNamespaces(reader, configuration);
1835                    nsURI = reader.getNamespaceURI();
1836                    name = reader.getLocalName();
1837                    if (XMLNS_SCXML.equals(nsURI)) {
1838                        if (ELEM_PARAM.equals(name)) {
1839                            if (send.getContent() == null) {
1840                                readParam(reader, configuration, send);
1841                            }
1842                            else {
1843                                reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
1844                            }
1845                        } else if (ELEM_CONTENT.equals(name)) {
1846                            if (send.getNamelist() == null && send.getParams().isEmpty()) {
1847                                readContent(reader, configuration, send);
1848                            }
1849                            else {
1850                                reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
1851                            }
1852                        } else {
1853                            reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
1854                        }
1855                    } else {
1856                        reportIgnoredElement(reader, configuration, ELEM_SEND, nsURI, name);
1857                    }
1858                    break;
1859                case XMLStreamConstants.END_ELEMENT:
1860                    popNamespaces(reader, configuration);
1861                    break loop;
1862                default:
1863            }
1864        }
1865
1866        send.setParent(executable);
1867        if (parent != null) {
1868            parent.addAction(send);
1869        } else {
1870            executable.addAction(send);
1871        }
1872    }
1873
1874    /**
1875     * Read the contents of this &lt;cancel&gt; element.
1876     *
1877     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1878     * @param configuration The {@link Configuration} to use while parsing.
1879     * @param executable The parent {@link Executable} for this action.
1880     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1881     *
1882     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1883     */
1884    private static void readCancel(final XMLStreamReader reader, final Configuration configuration,
1885                                   final Executable executable, final ActionsContainer parent)
1886            throws XMLStreamException, ModelException {
1887
1888        Cancel cancel = new Cancel();
1889        cancel.setSendid(readAV(reader, ATTR_SENDID));
1890        String attrValue = readAV(reader, ATTR_SENDIDEXPR);
1891        if (attrValue != null) {
1892            if (cancel.getSendid() != null) {
1893                reportConflictingAttribute(reader, configuration, ELEM_CANCEL, ATTR_SENDID, ATTR_SENDIDEXPR);
1894            }
1895            else {
1896                cancel.setSendidexpr(attrValue);
1897            }
1898        }
1899        readNamespaces(configuration, cancel);
1900        cancel.setParent(executable);
1901        if (parent != null) {
1902            parent.addAction(cancel);
1903        } else {
1904            executable.addAction(cancel);
1905        }
1906        skipToEndElement(reader);
1907    }
1908
1909    /**
1910     * Read the contents of this &lt;script&gt; element.
1911     *
1912     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1913     * @param configuration The {@link Configuration} to use while parsing.
1914     * @param executable The parent {@link Executable} for this action.
1915     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1916     *
1917     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1918     */
1919    private static void readScript(final XMLStreamReader reader, final Configuration configuration,
1920                                   final Executable executable, final ActionsContainer parent)
1921            throws XMLStreamException {
1922
1923        Script script = new Script();
1924        readNamespaces(configuration, script);
1925        script.setBody(readBody(reader));
1926        script.setParent(executable);
1927        if (parent != null) {
1928            parent.addAction(script);
1929        } else {
1930            executable.addAction(script);
1931        }
1932    }
1933
1934    /**
1935     * Read the contents of the initial &lt;script&gt; element.
1936     * @see <a href="http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml">
1937     *     http://www.w3.org/TR/2013/WD-scxml-20130801/#scxml<a> section 3.2.2
1938     *
1939     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1940     * @param configuration The {@link Configuration} to use while parsing.
1941     * @param scxml The root of the object model being parsed.
1942     *
1943     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1944     */
1945    private static void readGlobalScript(final XMLStreamReader reader, final Configuration configuration,
1946                                         final SCXML scxml)
1947            throws XMLStreamException {
1948
1949        Script globalScript = new Script();
1950        globalScript.setGlobalScript(true);
1951        readNamespaces(configuration, globalScript);
1952        globalScript.setBody(readBody(reader));
1953        scxml.setGlobalScript(globalScript);
1954    }
1955
1956    /**
1957     * Read the contents of this &lt;var&gt; element.
1958     *
1959     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1960     * @param configuration The {@link Configuration} to use while parsing.
1961     * @param executable The parent {@link Executable} for this action.
1962     * @param parent The optional parent {@link ActionsContainer} if this action is a child of one.
1963     *
1964     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1965     */
1966    private static void readVar(final XMLStreamReader reader, final Configuration configuration,
1967                                final Executable executable, final ActionsContainer parent)
1968            throws XMLStreamException {
1969
1970        Var var = new Var();
1971        var.setName(readAV(reader, ATTR_NAME));
1972        var.setExpr(readAV(reader, ATTR_EXPR));
1973        readNamespaces(configuration, var);
1974        var.setParent(executable);
1975        if (parent != null) {
1976            parent.addAction(var);
1977        } else {
1978            executable.addAction(var);
1979        }
1980        skipToEndElement(reader);
1981    }
1982
1983    /**
1984     * Read the contents of this custom action.
1985     *
1986     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
1987     * @param configuration The {@link Configuration} to use while parsing.
1988     * @param customAction The {@link CustomAction} to read.
1989     * @param executable The parent {@link Executable} for this custom action.
1990     * @param parent The optional parent {@link ActionsContainer} if this custom action is a child of one.
1991     *
1992     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
1993     */
1994    private static void readCustomAction(final XMLStreamReader reader, final Configuration configuration,
1995                                         final CustomAction customAction, final Executable executable,
1996                                         final ActionsContainer parent)
1997            throws XMLStreamException {
1998
1999        // Instantiate custom action
2000        Object actionObject;
2001        String className = customAction.getActionClass().getName();
2002        ClassLoader cl = configuration.customActionClassLoader;
2003        if (configuration.useContextClassLoaderForCustomActions) {
2004            cl = Thread.currentThread().getContextClassLoader();
2005        }
2006        if (cl == null) {
2007            cl = SCXMLReader.class.getClassLoader();
2008        }
2009        Class<?> clazz;
2010        try {
2011            clazz = cl.loadClass(className);
2012            actionObject = clazz.newInstance();
2013        } catch (ClassNotFoundException cnfe) {
2014            throw new XMLStreamException("Cannot find custom action class:" + className, cnfe);
2015        } catch (IllegalAccessException iae) {
2016            throw new XMLStreamException("Cannot access custom action class:" + className, iae);
2017        } catch (InstantiationException ie) {
2018            throw new XMLStreamException("Cannot instantiate custom action class:" + className, ie);
2019        }
2020        if (!(actionObject instanceof Action)) {
2021            throw new IllegalArgumentException(ERR_CUSTOM_ACTION_TYPE + className);
2022        }
2023
2024        // Set the attribute values as properties
2025        Action action = (Action) actionObject;
2026        for (int i = 0; i < reader.getAttributeCount(); i++) {
2027            String name = reader.getAttributeLocalName(i);
2028            String value = reader.getAttributeValue(i);
2029            String setter = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
2030            Method method;
2031            try {
2032                method = clazz.getMethod(setter, String.class);
2033                method.invoke(action, value);
2034            } catch (NoSuchMethodException nsme) {
2035                throw new XMLStreamException("No setter in class:" + className + ", for string property:" + name,
2036                        nsme);
2037            } catch (InvocationTargetException ite) {
2038                throw new XMLStreamException("Exception calling setter for string property:" + name + " in class:"
2039                        + className, ite);
2040            } catch (IllegalAccessException iae) {
2041                throw new XMLStreamException("Cannot access setter for string property:" + name + " in class:"
2042                        + className, iae);
2043            }
2044        }
2045
2046        // Add any body content if necessary
2047        if (action instanceof ExternalContent) {
2048            Node body = readNode(reader, configuration, customAction.getNamespaceURI(),
2049                    customAction.getLocalName(), new String [] {});
2050            NodeList childNodes = body.getChildNodes();
2051            List<Node> externalNodes = ((ExternalContent) action).getExternalNodes();
2052            for (int i = 0; i < childNodes.getLength(); i++) {
2053                externalNodes.add(childNodes.item(i));
2054            }
2055        }
2056        else {
2057            skipToEndElement(reader);
2058        }
2059
2060        // Wire in the action and add to parent
2061        readNamespaces(configuration, action);
2062        action.setParent(executable);
2063        if (parent != null) {
2064            parent.addAction(action);
2065        } else {
2066            executable.addAction(action);
2067        }
2068    }
2069
2070    /**
2071     * Read the following contents into a DOM {@link Node}.
2072     *
2073     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2074     * @param configuration The {@link Configuration} to use while parsing.
2075     * @param namespaceURI The namespace URI of the parent element
2076     * @param localName The local name of the parent element
2077     * @param attrs The attributes that will be read into the root DOM node.
2078     *
2079     * @return The parsed content as a DOM {@link Node}.
2080     *
2081     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2082     */
2083    private static Node readNode(final XMLStreamReader reader, final Configuration configuration,
2084                                 final String namespaceURI, final String localName, final String[] attrs)
2085            throws XMLStreamException {
2086
2087        // Create a document in which to build the DOM node
2088        Document document;
2089        try {
2090            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
2091        } catch (ParserConfigurationException pce) {
2092            throw new XMLStreamException(ERR_PARSER_CFG);
2093        }
2094
2095        // This root element will be returned, add any attributes as specified
2096        Element root = document.createElementNS(namespaceURI, localName);
2097        for (final String attr1 : attrs) {
2098            Attr attr = document.createAttributeNS(XMLNS_DEFAULT, attr1);
2099            attr.setValue(readAV(reader, attr1));
2100            root.setAttributeNodeNS(attr);
2101        }
2102        document.appendChild(root);
2103
2104        boolean children = false;
2105        Node parent = root;
2106
2107        // Convert stream to DOM node(s) while maintaining parent child relationships
2108        loop : while (reader.hasNext()) {
2109            String name, nsURI;
2110            Node child = null;
2111            switch (reader.next()) {
2112                case XMLStreamConstants.START_ELEMENT:
2113                    if (!children && root.hasChildNodes()) {
2114                        // remove any children
2115                        root.setTextContent(null);
2116                    }
2117                    children = true;
2118                    pushNamespaces(reader, configuration);
2119                    nsURI = reader.getNamespaceURI();
2120                    name = reader.getLocalName();
2121                    Element elem = document.createElementNS(nsURI, name);
2122                    for (int i = 0; i < reader.getAttributeCount(); i++) {
2123                        nsURI = reader.getAttributeNamespace(i);
2124                        name = reader.getAttributeLocalName(i);
2125                        String prefix = reader.getAttributePrefix(i);
2126                        if (prefix != null && prefix.length() > 0) {
2127                            name = prefix + ":" + name;
2128                        }
2129                        Attr attr = document.createAttributeNS(nsURI, name);
2130                        attr.setValue(reader.getAttributeValue(i));
2131                        elem.setAttributeNodeNS(attr);
2132                    }
2133                    parent.appendChild(elem);
2134                    parent = elem;
2135                    break;
2136                case XMLStreamConstants.SPACE:
2137                case XMLStreamConstants.CHARACTERS:
2138                case XMLStreamConstants.ENTITY_REFERENCE:
2139                    if (!children || parent != root) {
2140                        child = document.createTextNode(reader.getText());
2141                    }
2142                    break;
2143                case XMLStreamConstants.CDATA:
2144                    children = true;
2145                    child = document.createCDATASection(reader.getText());
2146                    break;
2147                case XMLStreamConstants.COMMENT:
2148                    children = true;
2149                    child = document.createComment(reader.getText());
2150                    break;
2151                case XMLStreamConstants.END_ELEMENT:
2152                    popNamespaces(reader, configuration);
2153                    parent = parent.getParentNode();
2154                    if (parent == document) {
2155                        break loop;
2156                    }
2157                    break;
2158                default: // rest is ignored
2159            }
2160            if (child != null) {
2161                parent.appendChild(child);
2162            }
2163        }
2164        if (!children && root.hasChildNodes()) {
2165            root.setTextContent(root.getTextContent().trim());
2166        }
2167        return root;
2168    }
2169
2170    /**
2171     * Read the following body contents into a String.
2172     *
2173     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2174     *
2175     * @return The body content read into a String.
2176     *
2177     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2178     */
2179    private static String readBody(final XMLStreamReader reader)
2180            throws XMLStreamException {
2181
2182        StringBuilder body = new StringBuilder();
2183        org.apache.commons.logging.Log log;
2184
2185        // Add all body content to StringBuilder
2186        loop : while (reader.hasNext()) {
2187            switch (reader.next()) {
2188                case XMLStreamConstants.START_ELEMENT:
2189                    log = LogFactory.getLog(SCXMLReader.class);
2190                    log.warn("Ignoring XML content in <script> element, encountered element with local name: "
2191                            + reader.getLocalName());
2192                    skipToEndElement(reader);
2193                    break;
2194                case XMLStreamConstants.SPACE:
2195                case XMLStreamConstants.CHARACTERS:
2196                case XMLStreamConstants.ENTITY_REFERENCE:
2197                case XMLStreamConstants.CDATA:
2198                case XMLStreamConstants.COMMENT:
2199                    body.append(reader.getText());
2200                    break;
2201                case XMLStreamConstants.END_ELEMENT:
2202                    break loop;
2203                default: // rest is ignored
2204            }
2205        }
2206        return body.toString();
2207    }
2208
2209    /**
2210     * @param input input string to check if null or empty after trim
2211     * @return null if input is null or empty after trim()
2212     */
2213    private static String nullIfEmpty(String input) {
2214        return input == null || input.trim().length()==0 ? null : input.trim();
2215    }
2216
2217    /**
2218     * Get the attribute value at the current reader location.
2219     *
2220     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2221     * @param attrLocalName The attribute name whose value is needed.
2222     *
2223     * @return The value of the attribute.
2224     */
2225    private static String readAV(final XMLStreamReader reader, final String attrLocalName) {
2226        return nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2227    }
2228
2229    /**
2230     * Get the Boolean attribute value at the current reader location.
2231     *
2232     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2233     * @param elementName The name of the element for which the attribute value is needed.
2234     * @param attrLocalName The attribute name whose value is needed.
2235     *
2236     * @return The Boolean value of the attribute.
2237     * @throws ModelException When the attribute value is not empty but neither "true" or "false".
2238     */
2239    private static Boolean readBooleanAV(final XMLStreamReader reader, final String elementName,
2240                                         final String attrLocalName)
2241            throws ModelException {
2242        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2243        Boolean result = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
2244        if (result == null && value != null) {
2245            MessageFormat msgFormat = new MessageFormat(ERR_ATTRIBUTE_NOT_BOOLEAN);
2246            String errMsg = msgFormat.format(new Object[] {value, attrLocalName, elementName, reader.getLocation()});
2247            throw new ModelException(errMsg);
2248        }
2249        return result;
2250    }
2251
2252    /**
2253     * Get a required attribute value at the current reader location,
2254     *
2255     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2256     * @param elementName The name of the element for which the attribute value is needed.
2257     * @param attrLocalName The attribute name whose value is needed.
2258     *
2259     * @return The value of the attribute.
2260     * @throws ModelException When the required attribute is missing or empty.
2261     */
2262    private static String readRequiredAV(final XMLStreamReader reader, final String elementName, final String attrLocalName)
2263            throws ModelException {
2264        String value = nullIfEmpty(reader.getAttributeValue(XMLNS_DEFAULT, attrLocalName));
2265        if (value == null) {
2266            MessageFormat msgFormat = new MessageFormat(ERR_REQUIRED_ATTRIBUTE_MISSING);
2267            String errMsg = msgFormat.format(new Object[] {elementName, attrLocalName, reader.getLocation()});
2268            throw new ModelException(errMsg);
2269        }
2270        return value;
2271    }
2272
2273    private static String readOrGeneratedTransitionTargetId(final XMLStreamReader reader, final SCXML scxml,
2274                                                            final String elementName)
2275            throws ModelException {
2276        String id = readAV(reader, ATTR_ID);
2277        if (id == null) {
2278            id = scxml.generateTransitionTargetId();
2279        }
2280        else if (id.startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
2281            MessageFormat msgFormat = new MessageFormat(ERR_RESERVED_ID_PREFIX);
2282            String errMsg = msgFormat.format(new Object[] {elementName, id, reader.getLocation()});
2283            throw new ModelException(errMsg);
2284        }
2285        return id;
2286    }
2287
2288    /**
2289     * Read the current active namespace declarations into the namespace prefixes holder.
2290     *
2291     * @param configuration The {@link Configuration} to use while parsing.
2292     * @param holder The {@link NamespacePrefixesHolder} to populate.
2293     */
2294    private static void readNamespaces(final Configuration configuration, final NamespacePrefixesHolder holder) {
2295
2296        holder.setNamespaces(configuration.getCurrentNamespaces());
2297    }
2298
2299    /**
2300     * Report an ignored element via the {@link XMLReporter} if available and the class
2301     * {@link org.apache.commons.logging.Log}.
2302     *
2303     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2304     * @param configuration The {@link Configuration} to use while parsing.
2305     * @param parent The parent element local name in the SCXML namespace.
2306     * @param nsURI The namespace URI of the ignored element.
2307     * @param name The local name of the ignored element.
2308     *
2309     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2310     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
2311     *                        errors in the SCXML document that may not be identified by the schema).
2312     */
2313    private static void reportIgnoredElement(final XMLStreamReader reader, final Configuration configuration,
2314                                             final String parent, final String nsURI, final String name)
2315            throws XMLStreamException, ModelException {
2316
2317        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
2318        StringBuilder sb = new StringBuilder();
2319        sb.append("Ignoring unknown or invalid element <").append(name)
2320                .append("> in namespace \"").append(nsURI)
2321                .append("\" as child of <").append(parent)
2322                .append("> at ").append(reader.getLocation());
2323        if (!configuration.isSilent() && log.isWarnEnabled()) {
2324            log.warn(sb.toString());
2325        }
2326        if (configuration.isStrict()) {
2327            throw new ModelException(sb.toString());
2328        }
2329        XMLReporter reporter = configuration.reporter;
2330        if (reporter != null) {
2331            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
2332        }
2333        skipToEndElement(reader);
2334    }
2335
2336    /**
2337     * Advances the XMLStreamReader until after the end of the current element: all children will be skipped as well
2338     * @param reader the reader
2339     * @throws XMLStreamException
2340     */
2341    private static void skipToEndElement(final XMLStreamReader reader) throws XMLStreamException {
2342        int elementsToSkip = 1;
2343        while (elementsToSkip > 0 && reader.hasNext()) {
2344            int next = reader.next();
2345            if (next == XMLStreamConstants.START_ELEMENT) {
2346                elementsToSkip++;
2347            }
2348            else if (next == XMLStreamConstants.END_ELEMENT) {
2349                elementsToSkip--;
2350            }
2351        }
2352    }
2353
2354    /**
2355     * Report an ignored attribute via the {@link XMLReporter} if available and the class
2356     * {@link org.apache.commons.logging.Log}.
2357     *
2358     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2359     * @param configuration The {@link Configuration} to use while parsing.
2360     * @param element The element name.
2361     * @param attr The attribute which is ignored.
2362     * @param value The value of the attribute which is ignored.
2363     *
2364     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2365     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
2366     *                        errors in the SCXML document that may not be identified by the schema).
2367     */
2368    private static void reportIgnoredAttribute(final XMLStreamReader reader, final Configuration configuration,
2369                                               final String element, final String attr, final String value)
2370            throws XMLStreamException, ModelException {
2371
2372        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
2373        StringBuilder sb = new StringBuilder();
2374        sb.append("Ignoring unknown or invalid <").append(element).append("> attribute ").append(attr)
2375                .append("=\"").append(value).append("\" at ").append(reader.getLocation());
2376        if (!configuration.isSilent() && log.isWarnEnabled()) {
2377            log.warn(sb.toString());
2378        }
2379        if (configuration.isStrict()) {
2380            throw new ModelException(sb.toString());
2381        }
2382        XMLReporter reporter = configuration.reporter;
2383        if (reporter != null) {
2384            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
2385        }
2386    }
2387
2388    /**
2389     * Report a conflicting attribute via the {@link XMLReporter} if available and the class
2390     * {@link org.apache.commons.logging.Log}.
2391     *
2392     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2393     * @param configuration The {@link Configuration} to use while parsing.
2394     * @param element The element name.
2395     * @param attr The attribute with which a conflict is detected.
2396     * @param conflictingAttr The conflicting attribute
2397     *
2398     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2399     * @throws ModelException The Commons SCXML object model is incomplete or inconsistent (includes
2400     *                        errors in the SCXML document that may not be identified by the schema).
2401     */
2402    private static void reportConflictingAttribute(final XMLStreamReader reader, final Configuration configuration,
2403                                             final String element, final String attr, final String conflictingAttr)
2404            throws XMLStreamException, ModelException {
2405
2406        org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLReader.class);
2407        StringBuilder sb = new StringBuilder();
2408        sb.append("Ignoring <").append(element).append("> attribute \"").append(conflictingAttr)
2409                .append("\" which conflicts with already defined attribute \"").append(attr)
2410                .append("\" at ").append(reader.getLocation());
2411        if (!configuration.isSilent() && log.isWarnEnabled()) {
2412            log.warn(sb.toString());
2413        }
2414        if (configuration.isStrict()) {
2415            throw new ModelException(sb.toString());
2416        }
2417        XMLReporter reporter = configuration.reporter;
2418        if (reporter != null) {
2419            reporter.report(sb.toString(), "COMMONS_SCXML", null, reader.getLocation());
2420        }
2421    }
2422
2423    /**
2424     * Push any new namespace declarations on the configuration namespaces map.
2425     *
2426     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2427     * @param configuration The {@link Configuration} to use while parsing.
2428     */
2429    private static void pushNamespaces(final XMLStreamReader reader, final Configuration configuration) {
2430
2431        for (int i = 0; i < reader.getNamespaceCount(); i++) {
2432            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
2433            if (stack == null) {
2434                stack = new Stack<String>();
2435                configuration.namespaces.put(reader.getNamespacePrefix(i), stack);
2436            }
2437            stack.push(reader.getNamespaceURI(i));
2438        }
2439    }
2440
2441    /**
2442     * Pop any expiring namespace declarations from the configuration namespaces map.
2443     *
2444     * @param reader The {@link XMLStreamReader} providing the SCXML document to parse.
2445     * @param configuration The {@link Configuration} to use while parsing.
2446     *
2447     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamReader}.
2448     */
2449    private static void popNamespaces(final XMLStreamReader reader, final Configuration configuration)
2450            throws XMLStreamException {
2451
2452        for (int i = 0; i < reader.getNamespaceCount(); i++) {
2453            Stack<String> stack = configuration.namespaces.get(reader.getNamespacePrefix(i));
2454            if (stack == null) {
2455                throw new XMLStreamException("Configuration namespaces stack null");
2456            }
2457            try {
2458                stack.pop();
2459                if (stack.empty()) {
2460                    configuration.namespaces.remove(reader.getNamespacePrefix(i));
2461                }
2462            } catch (EmptyStackException e) {
2463                throw new XMLStreamException("Configuration namespaces stack popped too many times");
2464            }
2465        }
2466    }
2467
2468    /**
2469     * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamReader} for this
2470     * {@link SCXMLReader}. Exactly one of the url, path, stream, reader or source parameters must be provided.
2471     *
2472     * @param configuration The {@link Configuration} to be used.
2473     * @param url The {@link URL} to the SCXML document to read.
2474     * @param path The optional real path to the SCXML document as a string.
2475     * @param stream The optional {@link InputStream} providing the SCXML document.
2476     * @param reader The optional {@link Reader} providing the SCXML document.
2477     * @param source The optional {@link Source} providing the SCXML document.
2478     *
2479     * @return The appropriately configured {@link XMLStreamReader}.
2480     *
2481     * @throws IOException Exception with the URL IO.
2482     * @throws XMLStreamException A problem with the XML stream creation or an wrapped {@link SAXException}
2483     *                            thrown in trying to validate the document against the XML Schema for SCXML.
2484     */
2485    private static XMLStreamReader getReader(final Configuration configuration, final URL url, final String path,
2486                                             final InputStream stream, final Reader reader, final Source source)
2487            throws IOException, XMLStreamException {
2488
2489        // Instantiate the XMLInputFactory
2490        XMLInputFactory factory = XMLInputFactory.newInstance();
2491        if (configuration.factoryId != null && configuration.factoryClassLoader != null) {
2492            factory = XMLInputFactory.newFactory(configuration.factoryId, configuration.factoryClassLoader);
2493        }
2494        factory.setEventAllocator(configuration.allocator);
2495        for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
2496            factory.setProperty(property.getKey(), property.getValue());
2497        }
2498        factory.setXMLReporter(configuration.reporter);
2499        factory.setXMLResolver(configuration.resolver);
2500
2501        // Consolidate InputStream options
2502        InputStream urlStream = null;
2503        if (url != null || path != null) {
2504            URL scxml = (url != null ? url : new URL(path));
2505            URLConnection conn = scxml.openConnection();
2506            conn.setUseCaches(false);
2507            urlStream = conn.getInputStream();
2508        } else if (stream != null) {
2509            urlStream = stream;
2510        }
2511
2512        // Create the XMLStreamReader
2513        XMLStreamReader xsr = null;
2514
2515        if (configuration.validate) {
2516            // Validation requires us to use a Source
2517
2518            URL scxmlSchema = new URL("TODO"); // TODO, point to appropriate location
2519            SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
2520            Schema schema;
2521            try {
2522                schema = schemaFactory.newSchema(scxmlSchema);
2523            } catch (SAXException se) {
2524                throw new XMLStreamException("Failed to create SCXML Schema for validation", se);
2525            }
2526
2527            Validator validator = schema.newValidator();
2528            validator.setErrorHandler(new SimpleErrorHandler());
2529
2530            Source src = null;
2531            if (urlStream != null) {
2532                // configuration.encoding is ignored
2533                if (configuration.systemId != null) {
2534                    src = new StreamSource(urlStream, configuration.systemId);
2535                } else {
2536                    src = new StreamSource(urlStream);
2537                }
2538            } else if (reader != null) {
2539                if (configuration.systemId != null) {
2540                    src = new StreamSource(reader, configuration.systemId);
2541                } else {
2542                    src = new StreamSource(reader);
2543                }
2544            } else if (source != null) {
2545                src = source;
2546            }
2547            xsr = factory.createXMLStreamReader(src);
2548            try {
2549                validator.validate(src);
2550            } catch (SAXException se) {
2551                throw new XMLStreamException("Failed to create apply SCXML Validator", se);
2552            }
2553
2554        } else {
2555            // We can use the more direct XMLInputFactory API if validation isn't needed
2556
2557            if (urlStream != null) {
2558                // systemId gets preference, then encoding if either are present
2559                if (configuration.systemId != null) {
2560                    xsr = factory.createXMLStreamReader(configuration.systemId, urlStream);
2561                } else if (configuration.encoding != null) {
2562                    xsr = factory.createXMLStreamReader(urlStream, configuration.encoding);
2563                } else {
2564                    xsr = factory.createXMLStreamReader(urlStream);
2565                }
2566            } else if (reader != null) {
2567                if (configuration.systemId != null) {
2568                    xsr = factory.createXMLStreamReader(configuration.systemId, reader);
2569                } else {
2570                    xsr = factory.createXMLStreamReader(reader);
2571                }
2572            } else if (source != null) {
2573                xsr = factory.createXMLStreamReader(source);
2574            }
2575
2576        }
2577
2578        return xsr;
2579    }
2580
2581    /**
2582     * Discourage instantiation since this is a utility class.
2583     */
2584    private SCXMLReader() {
2585        super();
2586    }
2587
2588    //------------------------- CONFIGURATION CLASS -------------------------//
2589    /**
2590     * <p>
2591     * Configuration for the {@link SCXMLReader}. The configuration properties necessary for the following are
2592     * covered:
2593     * </p>
2594     *
2595     * <ul>
2596     *   <li>{@link XMLInputFactory} configuration properties such as {@link XMLReporter}, {@link XMLResolver} and
2597     *   {@link XMLEventAllocator}</li>
2598     *   <li>{@link XMLStreamReader} configuration properties such as <code>systemId</code> and <code>encoding</code>
2599     *   </li>
2600     *   <li>Commons SCXML object model configuration properties such as the list of custom actions and the
2601     *   {@link PathResolver} to use.</li>
2602     * </ul>
2603     */
2604    public static class Configuration {
2605
2606        /*
2607         * Configuration properties for this {@link SCXMLReader}.
2608         */
2609        // XMLInputFactory configuration properties.
2610        /**
2611         * The <code>factoryId</code> to use for the {@link XMLInputFactory}.
2612         */
2613        final String factoryId;
2614
2615        /**
2616         * The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
2617         */
2618        final ClassLoader factoryClassLoader;
2619
2620        /**
2621         * The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2622         */
2623        final XMLEventAllocator allocator;
2624
2625        /**
2626         * The map of properties (keys are property name strings, values are object property values) for the
2627         * {@link XMLInputFactory}.
2628         */
2629        final Map<String, Object> properties;
2630
2631        /**
2632         * The {@link XMLResolver} for the {@link XMLInputFactory}.
2633         */
2634        final XMLResolver resolver;
2635
2636        /**
2637         * The {@link XMLReporter} for the {@link XMLInputFactory}.
2638         */
2639        final XMLReporter reporter;
2640
2641        // XMLStreamReader configuration properties.
2642        /**
2643         * The <code>encoding</code> to use for the {@link XMLStreamReader}.
2644         */
2645        final String encoding;
2646
2647        /**
2648         * The <code>systemId</code> to use for the {@link XMLStreamReader}.
2649         */
2650        final String systemId;
2651
2652        /**
2653         * Whether to validate the input with the XML Schema for SCXML.
2654         */
2655        final boolean validate;
2656
2657        // Commons SCXML object model configuration properties.
2658        /**
2659         * The list of Commons SCXML custom actions that will be available for this document.
2660         */
2661        final List<CustomAction> customActions;
2662
2663        /**
2664         * The {@link ClassLoader} to use for loading the {@link CustomAction} instances to create.
2665         */
2666        final ClassLoader customActionClassLoader;
2667
2668        /**
2669         * Whether to use the thread context {@link ClassLoader} for loading any {@link CustomAction} classes.
2670         */
2671        final boolean useContextClassLoaderForCustomActions;
2672
2673        /**
2674         * The map for bookkeeping the current active namespace declarations. The keys are prefixes and the values are
2675         * {@link Stack}s containing the corresponding namespaceURIs, with the active one on top.
2676         */
2677        final Map<String, Stack<String>> namespaces;
2678
2679        // Mutable Commons SCXML object model configuration properties.
2680        /**
2681         * The parent SCXML document if this document is src'ed in via the &lt;state&gt; or &lt;parallel&gt; element's
2682         * "src" attribute.
2683         */
2684        SCXML parent;
2685
2686        /**
2687         * The Commons SCXML {@link PathResolver} to use for this document.
2688         */
2689        PathResolver pathResolver;
2690
2691        /**
2692         * Whether to silently ignore any unknown or invalid elements
2693         * or to leave warning logs for those.
2694         */
2695        boolean silent;
2696
2697        /**
2698         * Whether to strictly throw a model exception when there are any unknown or invalid elements
2699         * or to leniently allow to read the model even with those.
2700         */
2701        boolean strict;
2702
2703        /*
2704         * Public constructors
2705         */
2706        /**
2707         * Default constructor.
2708         */
2709        public Configuration() {
2710            this(null, null);
2711        }
2712
2713        /**
2714         * Minimal convenience constructor.
2715         *
2716         * @param reporter The {@link XMLReporter} to use for this reading.
2717         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
2718         */
2719        public Configuration(final XMLReporter reporter, final PathResolver pathResolver) {
2720            this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, null, null, false);
2721        }
2722
2723        /**
2724         * Convenience constructor.
2725         *
2726         * @param reporter The {@link XMLReporter} to use for this reading.
2727         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this reading.
2728         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2729         */
2730        public Configuration(final XMLReporter reporter, final PathResolver pathResolver,
2731                             final List<CustomAction> customActions) {
2732            this(null, null, null, null, null, reporter, null, null, false, pathResolver, null, customActions, null,
2733                    false);
2734        }
2735
2736        /**
2737         * All purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default
2738         * to <code>false</code>).
2739         *
2740         * @param factoryId The <code>factoryId</code> to use.
2741         * @param classLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to create.
2742         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2743         * @param properties The map of properties (keys are property name strings, values are object property values)
2744         *                   for the {@link XMLInputFactory}.
2745         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2746         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2747         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2748         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2749         * @param validate Whether to validate the input with the XML Schema for SCXML.
2750         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2751         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2752         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2753         *                                create.
2754         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2755         *                                             {@link CustomAction} instances to create.
2756         */
2757        public Configuration(final String factoryId, final ClassLoader classLoader, final XMLEventAllocator allocator,
2758                             final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2759                             final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2760                             final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2761                             final boolean useContextClassLoaderForCustomActions) {
2762            this(factoryId, classLoader, allocator, properties, resolver, reporter, encoding, systemId, validate,
2763                    pathResolver, null, customActions, customActionClassLoader,
2764                    useContextClassLoaderForCustomActions);
2765        }
2766
2767        /*
2768         * Package access constructors
2769         */
2770        /**
2771         * Convenience package access constructor.
2772         *
2773         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2774         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2775         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2776         *               &lt;parallel&gt; element's "src" attribute.
2777         */
2778        Configuration(final XMLReporter reporter, final PathResolver pathResolver, final SCXML parent) {
2779            this(null, null, null, null, null, reporter, null, null, false, pathResolver, parent, null, null, false);
2780        }
2781
2782        /**
2783         * Package access copy constructor.
2784         *
2785         * @param source The source {@link Configuration} to replicate.
2786         */
2787        Configuration(final Configuration source) {
2788            this(source.factoryId, source.factoryClassLoader, source.allocator, source.properties, source.resolver,
2789                    source.reporter, source.encoding, source.systemId, source.validate, source.pathResolver,
2790                    source.parent, source.customActions, source.customActionClassLoader,
2791                    source.useContextClassLoaderForCustomActions, source.silent, source.strict);
2792        }
2793
2794        /**
2795         * All-purpose package access constructor.
2796         *
2797         * @param factoryId The <code>factoryId</code> to use.
2798         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
2799         *                           create.
2800         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2801         * @param properties The map of properties (keys are property name strings, values are object property values)
2802         *                   for the {@link XMLInputFactory}.
2803         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2804         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2805         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2806         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2807         * @param validate Whether to validate the input with the XML Schema for SCXML.
2808         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2809         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2810         *               &lt;parallel&gt; element's "src" attribute.
2811         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2812         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2813         *                                create.
2814         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2815         *                                             {@link CustomAction} instances to create.
2816         */
2817        Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
2818                      final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2819                      final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2820                      final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2821                      final boolean useContextClassLoaderForCustomActions) {
2822            this(factoryId, factoryClassLoader, allocator, properties, resolver, reporter, encoding, systemId,
2823                    validate, pathResolver, parent, customActions, customActionClassLoader,
2824                    useContextClassLoaderForCustomActions, false, false);
2825        }
2826
2827        /**
2828         * All-purpose package access constructor.
2829         *
2830         * @param factoryId The <code>factoryId</code> to use.
2831         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLInputFactory} instance to
2832         *                           create.
2833         * @param allocator The {@link XMLEventAllocator} for the {@link XMLInputFactory}.
2834         * @param properties The map of properties (keys are property name strings, values are object property values)
2835         *                   for the {@link XMLInputFactory}.
2836         * @param resolver The {@link XMLResolver} for the {@link XMLInputFactory}.
2837         * @param reporter The {@link XMLReporter} for the {@link XMLInputFactory}.
2838         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamReader}
2839         * @param systemId The <code>systemId</code> to use for the {@link XMLStreamReader}
2840         * @param validate Whether to validate the input with the XML Schema for SCXML.
2841         * @param pathResolver The Commons SCXML {@link PathResolver} to use for this document.
2842         * @param parent The parent SCXML document if this document is src'ed in via the &lt;state&gt; or
2843         *               &lt;parallel&gt; element's "src" attribute.
2844         * @param customActions The list of Commons SCXML custom actions that will be available for this document.
2845         * @param customActionClassLoader The {@link ClassLoader} to use for the {@link CustomAction} instances to
2846         *                                create.
2847         * @param useContextClassLoaderForCustomActions Whether to use the thread context {@link ClassLoader} for the
2848         *                                             {@link CustomAction} instances to create.
2849         * @param silent Whether to silently ignore any unknown or invalid elements or to leave warning logs for those.
2850         * @param strict Whether to strictly throw a model exception when there are any unknown or invalid elements
2851         *               or to leniently allow to read the model even with those.
2852         */
2853        Configuration(final String factoryId, final ClassLoader factoryClassLoader, final XMLEventAllocator allocator,
2854                      final Map<String, Object> properties, final XMLResolver resolver, final XMLReporter reporter,
2855                      final String encoding, final String systemId, final boolean validate, final PathResolver pathResolver,
2856                      final SCXML parent, final List<CustomAction> customActions, final ClassLoader customActionClassLoader,
2857                      final boolean useContextClassLoaderForCustomActions, final boolean silent, final boolean strict) {
2858            this.factoryId = factoryId;
2859            this.factoryClassLoader = factoryClassLoader;
2860            this.allocator = allocator;
2861            this.properties = (properties == null ? new HashMap<String, Object>() : properties);
2862            this.resolver = resolver;
2863            this.reporter = reporter;
2864            this.encoding = encoding;
2865            this.systemId = systemId;
2866            this.validate = validate;
2867            this.pathResolver = pathResolver;
2868            this.parent = parent;
2869            this.customActions = (customActions == null ? new ArrayList<CustomAction>() : customActions);
2870            this.customActionClassLoader = customActionClassLoader;
2871            this.useContextClassLoaderForCustomActions = useContextClassLoaderForCustomActions;
2872            this.namespaces = new HashMap<String, Stack<String>>();
2873            this.silent = silent;
2874            this.strict = strict;
2875        }
2876
2877        /*
2878         * Package access convenience methods
2879         */
2880        /**
2881         * Get the current namespaces at this point in the StAX reading.
2882         *
2883         * @return Map<String,String> The namespace map (keys are prefixes and values are the corresponding current
2884         *                            namespace URIs).
2885         */
2886        Map<String, String> getCurrentNamespaces() {
2887            Map<String, String> currentNamespaces = new HashMap<String, String>();
2888            for (Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet()) {
2889                currentNamespaces.put(nsEntry.getKey(), nsEntry.getValue().peek());
2890            }
2891            return currentNamespaces;
2892        }
2893
2894        /**
2895         * Returns true if it is set to read models silently without any model error warning logs.
2896         * @return
2897         * @see {@link #silent}
2898         */
2899        public boolean isSilent() {
2900            return silent;
2901        }
2902
2903        /**
2904         * Turn on/off silent mode (whether to read models silently without any model error warning logs)
2905         * @param silent
2906         * @see {@link #silent}
2907         */
2908        public void setSilent(boolean silent) {
2909            this.silent = silent;
2910        }
2911
2912        /**
2913         * Returns true if it is set to check model strictly with throwing exceptions on any model error.
2914         * @return
2915         * @see {@link #strict}
2916         */
2917        public boolean isStrict() {
2918            return strict;
2919        }
2920
2921        /**
2922         * Turn on/off strict model (whether to check model strictly with throwing exception on any model error)
2923         * @param strict
2924         * @see {@link #strict}
2925         */
2926        public void setStrict(boolean strict) {
2927            this.strict = strict;
2928        }
2929    }
2930}