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.OutputStream;
021import java.io.StringReader;
022import java.io.StringWriter;
023import java.io.Writer;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028
029import javax.xml.stream.XMLOutputFactory;
030import javax.xml.stream.XMLStreamException;
031import javax.xml.stream.XMLStreamWriter;
032import javax.xml.transform.OutputKeys;
033import javax.xml.transform.Result;
034import javax.xml.transform.Source;
035import javax.xml.transform.Transformer;
036import javax.xml.transform.TransformerConfigurationException;
037import javax.xml.transform.TransformerException;
038import javax.xml.transform.TransformerFactory;
039import javax.xml.transform.TransformerFactoryConfigurationError;
040import javax.xml.transform.dom.DOMSource;
041import javax.xml.transform.stream.StreamResult;
042import javax.xml.transform.stream.StreamSource;
043
044import org.apache.commons.logging.LogFactory;
045import org.apache.commons.scxml2.model.Action;
046import org.apache.commons.scxml2.model.Assign;
047import org.apache.commons.scxml2.model.Cancel;
048import org.apache.commons.scxml2.model.Content;
049import org.apache.commons.scxml2.model.Data;
050import org.apache.commons.scxml2.model.Datamodel;
051import org.apache.commons.scxml2.model.Else;
052import org.apache.commons.scxml2.model.ElseIf;
053import org.apache.commons.scxml2.model.EnterableState;
054import org.apache.commons.scxml2.model.Raise;
055import org.apache.commons.scxml2.model.ExternalContent;
056import org.apache.commons.scxml2.model.Final;
057import org.apache.commons.scxml2.model.Finalize;
058import org.apache.commons.scxml2.model.Foreach;
059import org.apache.commons.scxml2.model.History;
060import org.apache.commons.scxml2.model.If;
061import org.apache.commons.scxml2.model.Initial;
062import org.apache.commons.scxml2.model.Invoke;
063import org.apache.commons.scxml2.model.Log;
064import org.apache.commons.scxml2.model.OnEntry;
065import org.apache.commons.scxml2.model.OnExit;
066import org.apache.commons.scxml2.model.Parallel;
067import org.apache.commons.scxml2.model.Param;
068import org.apache.commons.scxml2.model.SCXML;
069import org.apache.commons.scxml2.model.Script;
070import org.apache.commons.scxml2.model.Send;
071import org.apache.commons.scxml2.model.SimpleTransition;
072import org.apache.commons.scxml2.model.State;
073import org.apache.commons.scxml2.model.Transition;
074import org.apache.commons.scxml2.model.TransitionTarget;
075import org.apache.commons.scxml2.model.Var;
076import org.w3c.dom.Node;
077import org.w3c.dom.NodeList;
078
079/**
080 * <p>Utility class for serializing the Commons SCXML Java object
081 * model. Class uses the visitor pattern to trace through the
082 * object heirarchy. Used primarily for testing, debugging and
083 * visual verification.</p>
084 *
085 * <b>NOTE:</b> This writer makes the following assumptions about the
086 * original SCXML document(s) parsed to create the object model:
087 * <ul>
088 *  <li>The default document namespace is the SCXML namespace:
089 *      <i>http://www.w3.org/2005/07/scxml</i></li>
090 *  <li>The Commons SCXML namespace
091 *      ( <i>http://commons.apache.org/scxml</i> ), if needed, uses the
092 *      &quot;<i>cs</i>&quot; prefix</li>
093 *  <li>All namespace prefixes needed throughout the document are
094 *      declared on the document root element (&lt;scxml&gt;)</li>
095 * </ul>
096 *
097 * @since 1.0
098 */
099public class SCXMLWriter {
100
101    //---------------------- PRIVATE CONSTANTS ----------------------//
102    //---- NAMESPACES ----//
103    /**
104     * The SCXML namespace.
105     */
106    private static final String XMLNS_SCXML = "http://www.w3.org/2005/07/scxml";
107
108    /**
109     * The Commons SCXML namespace.
110     */
111    private static final String XMLNS_COMMONS_SCXML = "http://commons.apache.org/scxml";
112
113    //---- ERROR MESSAGES ----//
114    /**
115     * Null OutputStream passed as argument.
116     */
117    private static final String ERR_NULL_OSTR = "Cannot write to null OutputStream";
118
119    /**
120     * Null Writer passed as argument.
121     */
122    private static final String ERR_NULL_WRIT = "Cannot write to null Writer";
123
124    /**
125     * Null Result passed as argument.
126     */
127    private static final String ERR_NULL_RES = "Cannot parse null Result";
128
129    //--------------------------- XML VOCABULARY ---------------------------//
130    //---- ELEMENT NAMES ----//
131    private static final String ELEM_ASSIGN = "assign";
132    private static final String ELEM_CANCEL = "cancel";
133    private static final String ELEM_CONTENT = "content";
134    private static final String ELEM_DATA = "data";
135    private static final String ELEM_DATAMODEL = "datamodel";
136    private static final String ELEM_ELSE = "else";
137    private static final String ELEM_ELSEIF = "elseif";
138    private static final String ELEM_RAISE = "raise";
139    private static final String ELEM_FINAL = "final";
140    private static final String ELEM_FINALIZE = "finalize";
141    private static final String ELEM_HISTORY = "history";
142    private static final String ELEM_IF = "if";
143    private static final String ELEM_INITIAL = "initial";
144    private static final String ELEM_INVOKE = "invoke";
145    private static final String ELEM_FOREACH = "foreach";
146    private static final String ELEM_LOG = "log";
147    private static final String ELEM_ONENTRY = "onentry";
148    private static final String ELEM_ONEXIT = "onexit";
149    private static final String ELEM_PARALLEL = "parallel";
150    private static final String ELEM_PARAM = "param";
151    private static final String ELEM_SCRIPT = "script";
152    private static final String ELEM_SCXML = "scxml";
153    private static final String ELEM_SEND = "send";
154    private static final String ELEM_STATE = "state";
155    private static final String ELEM_TRANSITION = "transition";
156    private static final String ELEM_VAR = "var";
157
158    //---- ATTRIBUTE NAMES ----//
159    private static final String ATTR_ARRAY = "array";
160    private static final String ATTR_ATTR = "attr";
161    private static final String ATTR_AUTOFORWARD = "autoforward";
162    private static final String ATTR_COND = "cond";
163    private static final String ATTR_DATAMODEL = "datamodel";
164    private static final String ATTR_DELAY = "delay";
165    private static final String ATTR_DELAYEXPR = "delayexpr";
166    private static final String ATTR_EVENT = "event";
167    private static final String ATTR_EVENTEXPR = "eventexpr";
168    private static final String ATTR_EXMODE = "exmode";
169    private static final String ATTR_EXPR = "expr";
170    private static final String ATTR_HINTS = "hints";
171    private static final String ATTR_ID = "id";
172    private static final String ATTR_IDLOCATION = "idlocation";
173    private static final String ATTR_INDEX = "index";
174    private static final String ATTR_INITIAL = "initial";
175    private static final String ATTR_ITEM = "item";
176    private static final String ATTR_LABEL = "label";
177    private static final String ATTR_LOCATION = "location";
178    private static final String ATTR_NAME = "name";
179    private static final String ATTR_NAMELIST = "namelist";
180    private static final String ATTR_PROFILE = "profile";
181    private static final String ATTR_SENDID = "sendid";
182    private static final String ATTR_SRC = "src";
183    private static final String ATTR_SRCEXPR = "srcexpr";
184    private static final String ATTR_TARGET = "target";
185    private static final String ATTR_TARGETEXPR = "targetexpr";
186    private static final String ATTR_TYPE = "type";
187    private static final String ATTR_TYPEEXPR = "typeexpr";
188    private static final String ATTR_VERSION = "version";
189
190    //------------------------- STATIC MEMBERS -------------------------//
191    /**
192     * The JAXP transformer.
193     */
194    private static final Transformer XFORMER = getTransformer();
195
196    //------------------------- PUBLIC API METHODS -------------------------//
197    /**
198     * Write out the Commons SCXML object model as an SCXML document (used
199     * primarily for testing, debugging and visual verification), returned as
200     * a string.
201     *
202     * @param scxml The object model to serialize.
203     *
204     * @return The corresponding SCXML document as a string.
205     *
206     * @throws IOException An IO error during serialization.
207     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
208     */
209    public static String write(final SCXML scxml)
210            throws IOException, XMLStreamException {
211
212        return write(scxml, new Configuration(true, true));
213    }
214
215    /**
216     * Write out the Commons SCXML object model as an SCXML document (used
217     * primarily for testing, debugging and visual verification) using the
218     * supplied {@link Configuration}, and return as a string.
219     *
220     * @param scxml The object model to serialize.
221     * @param configuration The {@link Configuration} to use while serializing.
222     *
223     * @return The corresponding SCXML document as a string.
224     *
225     * @throws IOException An IO error during serialization.
226     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
227     */
228    public static String write(final SCXML scxml, final Configuration configuration)
229            throws IOException, XMLStreamException {
230
231        // Must be true since we want to return a string
232        configuration.writeToString = true;
233        writeInternal(scxml, configuration, null, null, null);
234        if (configuration.usePrettyPrint) {
235            return configuration.prettyPrintOutput;
236        } else {
237            configuration.internalWriter.flush();
238            return configuration.internalWriter.toString();
239        }
240    }
241
242    /**
243     * Write out the Commons SCXML object model as an SCXML document to the
244     * supplied {@link OutputStream}.
245     *
246     * @param scxml The object model to write out.
247     * @param scxmlStream The {@link OutputStream} to write to.
248     *
249     * @throws IOException An IO error during serialization.
250     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
251     */
252    public static void write(final SCXML scxml, final OutputStream scxmlStream)
253            throws IOException, XMLStreamException {
254
255        write(scxml, scxmlStream, new Configuration());
256    }
257
258    /**
259     * Write out the Commons SCXML object model as an SCXML document to the
260     * supplied {@link OutputStream} using the given {@link Configuration}.
261     *
262     * @param scxml The object model to write out.
263     * @param scxmlStream The {@link OutputStream} to write to.
264     * @param configuration The {@link Configuration} to use.
265     *
266     * @throws IOException An IO error during serialization.
267     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
268     */
269    public static void write(final SCXML scxml, final OutputStream scxmlStream, final Configuration configuration)
270            throws IOException, XMLStreamException {
271
272        if (scxmlStream == null) {
273            throw new IllegalArgumentException(ERR_NULL_OSTR);
274        }
275        writeInternal(scxml, configuration, scxmlStream, null, null);
276        if (configuration.closeUnderlyingWhenDone) {
277            scxmlStream.flush();
278            scxmlStream.close();
279        }
280    }
281
282    /**
283     * Write out the Commons SCXML object model as an SCXML document to the
284     * supplied {@link Writer}.
285     *
286     * @param scxml The object model to write out.
287     * @param scxmlWriter The {@link Writer} to write to.
288     *
289     * @throws IOException An IO error during serialization.
290     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
291     */
292    public static void write(final SCXML scxml, final Writer scxmlWriter)
293            throws IOException, XMLStreamException {
294
295        write(scxml, scxmlWriter, new Configuration());
296    }
297
298    /**
299     * Write out the Commons SCXML object model as an SCXML document to the
300     * supplied {@link Writer} using the given {@link Configuration}.
301     *
302     * @param scxml The object model to write out.
303     * @param scxmlWriter The {@link Writer} to write to.
304     * @param configuration The {@link Configuration} to use.
305     *
306     * @throws IOException An IO error during serialization.
307     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
308     */
309    public static void write(final SCXML scxml, final Writer scxmlWriter, final Configuration configuration)
310            throws IOException, XMLStreamException {
311
312        if (scxmlWriter == null) {
313            throw new IllegalArgumentException(ERR_NULL_WRIT);
314        }
315        writeInternal(scxml, configuration, null, scxmlWriter, null);
316        if (configuration.closeUnderlyingWhenDone) {
317            scxmlWriter.flush();
318            scxmlWriter.close();
319        }
320    }
321
322    /**
323     * Write out the Commons SCXML object model as an SCXML document to the
324     * supplied {@link Result}.
325     *
326     * @param scxml The object model to write out.
327     * @param scxmlResult The {@link Result} to write to.
328     *
329     * @throws IOException An IO error during serialization.
330     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
331     */
332    public static void write(final SCXML scxml, final Result scxmlResult)
333            throws IOException, XMLStreamException {
334
335        write(scxml, scxmlResult, new Configuration());
336    }
337
338    /**
339     * Write out the Commons SCXML object model as an SCXML document to the
340     * supplied {@link Result} using the given {@link Configuration}.
341     *
342     * @param scxml The object model to write out.
343     * @param scxmlResult The {@link Result} to write to.
344     * @param configuration The {@link Configuration} to use.
345     *
346     * @throws IOException An IO error during serialization.
347     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
348     */
349    public static void write(final SCXML scxml, final Result scxmlResult, final Configuration configuration)
350            throws IOException, XMLStreamException {
351
352        if (scxmlResult == null) {
353            throw new IllegalArgumentException(ERR_NULL_RES);
354        }
355        writeInternal(scxml, configuration, null, null, scxmlResult);
356    }
357
358    //---------------------- PRIVATE UTILITY METHODS ----------------------//
359
360    /**
361     * Escape XML strings for serialization.
362     * The basic algorithm is taken from Commons Lang (see oacl.Entities.java)
363     *
364     * @param str A string to be escaped
365     * @return The escaped string
366     */
367    private static String escapeXML(final String str) {
368        if (str == null) {
369            return null;
370        }
371
372        // Make the writer an arbitrary bit larger than the source string
373        int len = str.length();
374        StringWriter stringWriter = new StringWriter(len + 8);
375
376        for (int i = 0; i < len; i++) {
377            char c = str.charAt(i);
378            String entityName = null; // Look for XML 1.0 predefined entities
379            switch (c) {
380                case '"':
381                    entityName = "quot";
382                    break;
383                case '&':
384                    entityName = "amp";
385                    break;
386                case '<':
387                    entityName = "lt";
388                    break;
389                case '>':
390                    entityName = "gt";
391                    break;
392                default:
393            }
394            if (entityName == null) {
395                if (c > 0x7F) {
396                    stringWriter.write("&#");
397                    stringWriter.write(Integer.toString(c));
398                    stringWriter.write(';');
399                } else {
400                    stringWriter.write(c);
401                }
402            } else {
403                stringWriter.write('&');
404                stringWriter.write(entityName);
405                stringWriter.write(';');
406            }
407        }
408
409        return stringWriter.toString();
410    }
411
412    /**
413     * Write out the Commons SCXML object model using the supplied {@link Configuration}.
414     * Exactly one of the stream, writer or result parameters must be provided.
415     *
416     * @param scxml The object model to write out.
417     * @param configuration The {@link Configuration} to use.
418     * @param scxmlStream The optional {@link OutputStream} to write to.
419     * @param scxmlWriter The optional {@link Writer} to write to.
420     * @param scxmlResult The optional {@link Result} to write to.
421     *
422     * @throws IOException An IO error during serialization.
423     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
424     */
425    private static void writeInternal(final SCXML scxml, final Configuration configuration,
426                                      final OutputStream scxmlStream, final Writer scxmlWriter, final Result scxmlResult)
427            throws IOException, XMLStreamException {
428
429        XMLStreamWriter writer = getWriter(configuration, scxmlStream, scxmlWriter, scxmlResult);
430        writeDocument(writer, configuration, scxml);
431        writer.flush();
432        writer.close();
433        if (configuration.internalWriter != null) {
434            configuration.internalWriter.flush();
435        }
436        if (configuration.usePrettyPrint) {
437            Writer prettyPrintWriter = (scxmlWriter != null ? scxmlWriter : new StringWriter());
438            writePretty(configuration, scxmlStream, prettyPrintWriter, scxmlResult);
439            if (configuration.writeToString) {
440                prettyPrintWriter.flush();
441                configuration.prettyPrintOutput = prettyPrintWriter.toString();
442            }
443        }
444    }
445
446    /**
447     * Write out the Commons SCXML object model as an SCXML document using the supplied {@link Configuration}.
448     * This method tackles the XML document level concerns.
449     *
450     * @param writer The {@link XMLStreamWriter} in use for the serialization.
451     * @param configuration The {@link Configuration} in use.
452     * @param scxml The root of the object model to write out.
453     *
454     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
455     */
456    private static void writeDocument(final XMLStreamWriter writer, final Configuration configuration,
457                                      final SCXML scxml)
458            throws XMLStreamException {
459
460        String encoding = "UTF-8";
461        if (configuration.encoding != null) {
462            encoding = configuration.encoding;
463        }
464        writer.writeStartDocument(encoding, "1.0");
465        writeSCXML(writer, scxml);
466        writer.writeEndDocument();
467    }
468
469    /**
470     * Write out this {@link SCXML} object into its serialization as the corresponding &lt;scxml&gt; element.
471     *
472     * @param writer The {@link XMLStreamWriter} in use for the serialization.
473     * @param scxml The root of the object model to write out.
474     *
475     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
476     */
477    private static void writeSCXML(final XMLStreamWriter writer, final SCXML scxml)
478            throws XMLStreamException {
479
480        // Start
481        writer.writeStartElement(ELEM_SCXML);
482
483        // Namespaces
484        writer.writeNamespace(null, XMLNS_SCXML);
485        writer.writeNamespace("cs", XMLNS_COMMONS_SCXML);
486        for (Map.Entry<String, String> entry : scxml.getNamespaces().entrySet()) {
487            String key = entry.getKey();
488            if (key != null && key.trim().length() > 0 && !key.equals("cs")) { // TODO Remove reserved prefixes
489                writer.writeNamespace(key, entry.getValue());
490            }
491        }
492
493        // Attributes
494        writeAV(writer, ATTR_VERSION, scxml.getVersion());
495        writeAV(writer, ATTR_INITIAL, scxml.getInitial());
496        writeAV(writer, ATTR_DATAMODEL, scxml.getDatamodelName());
497        writeAV(writer, ATTR_NAME, scxml.getName());
498        writeAV(writer, ATTR_PROFILE, scxml.getProfile());
499        writeAV(writer, ATTR_EXMODE, scxml.getExmode());
500
501        // Marker to indicate generated document
502        writer.writeComment(XMLNS_COMMONS_SCXML);
503
504        // Write global script if defined
505        if (scxml.getGlobalScript() != null) {
506            Script s = scxml.getGlobalScript();
507            writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
508            writer.writeCData(s.getScript());
509            writer.writeEndElement();
510        }
511
512        // Children
513        writeDatamodel(writer, scxml.getDatamodel());
514        for (EnterableState es : scxml.getChildren()) {
515            if (es instanceof Final) {
516                writeFinal(writer, (Final) es);
517            } else if (es instanceof State) {
518                writeState(writer, (State) es);
519            } else if (es instanceof Parallel) {
520                writeParallel(writer, (Parallel) es);
521            }
522        }
523
524        // End
525        writer.writeEndElement();
526    }
527
528    /**
529     * Write out this {@link Datamodel} object into its serialization as the corresponding &lt;datamodel&gt; element.
530     *
531     * @param writer The {@link XMLStreamWriter} in use for the serialization.
532     * @param datamodel The {@link Datamodel} to serialize.
533     *
534     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
535     */
536    private static void writeDatamodel(final XMLStreamWriter writer, final Datamodel datamodel)
537            throws XMLStreamException {
538
539        if (datamodel == null) {
540            return;
541        }
542
543        writer.writeStartElement(ELEM_DATAMODEL);
544        if (datamodel.getData().size() > 0 && XFORMER == null) {
545            writer.writeComment("Datamodel was not serialized");
546        } else {
547            for (Data d : datamodel.getData()) {
548                Node n = d.getNode();
549                if (n != null) {
550                    writeNode(writer, n);
551                } else {
552                    writer.writeStartElement(ELEM_DATA);
553                    writeAV(writer, ATTR_ID, d.getId());
554                    writeAV(writer, ATTR_SRC, escapeXML(d.getSrc()));
555                    writeAV(writer, ATTR_EXPR, escapeXML(d.getExpr()));
556                    writer.writeEndElement();
557                }
558            }
559        }
560        writer.writeEndElement();
561    }
562
563    /**
564     * Write out the TransitionTarget id attribute unless it was auto-generated
565     * @param writer The {@link XMLStreamWriter} in use for the serialization.
566     * @param tt The {@link TransitionTarget} for which to write the id attribute.
567     * @throws XMLStreamException
568     */
569    private static void writeTransitionTargetId(final XMLStreamWriter writer, final TransitionTarget tt)
570            throws XMLStreamException {
571        if (!tt.getId().startsWith(SCXML.GENERATED_TT_ID_PREFIX)) {
572            writeAV(writer, ATTR_ID, tt.getId());
573        }
574    }
575
576    /**
577     * Write out this {@link State} object into its serialization as the corresponding &lt;state&gt; element.
578     *
579     * @param writer The {@link XMLStreamWriter} in use for the serialization.
580     * @param state The {@link State} to serialize.
581     *
582     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
583     */
584    private static void writeState(final XMLStreamWriter writer, final State state)
585            throws XMLStreamException {
586
587        writer.writeStartElement(ELEM_STATE);
588        writeTransitionTargetId(writer, state);
589        writeAV(writer, ATTR_INITIAL, state.getFirst());
590        writeInitial(writer, state.getInitial());
591        writeDatamodel(writer, state.getDatamodel());
592        writeHistory(writer, state.getHistory());
593        for (OnEntry onentry : state.getOnEntries()) {
594            writeOnEntry(writer, onentry);
595        }
596
597        for (Transition t : state.getTransitionsList()) {
598            writeTransition(writer, t);
599        }
600
601        for (Invoke inv : state.getInvokes()) {
602            writeInvoke(writer, inv);
603        }
604
605        for (EnterableState es : state.getChildren()) {
606            if (es instanceof Final) {
607                writeFinal(writer, (Final) es);
608            } else if (es instanceof State) {
609                writeState(writer, (State) es);
610            } else if (es instanceof Parallel) {
611                writeParallel(writer, (Parallel) es);
612            }
613        }
614
615        for (OnExit onexit : state.getOnExits()) {
616            writeOnExit(writer, onexit);
617        }
618        writer.writeEndElement();
619    }
620
621    /**
622     * Write out this {@link Parallel} object into its serialization as the corresponding &lt;parallel&gt; element.
623     *
624     * @param writer The {@link XMLStreamWriter} in use for the serialization.
625     * @param parallel The {@link Parallel} to serialize.
626     *
627     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
628     */
629    private static void writeParallel(final XMLStreamWriter writer, final Parallel parallel)
630            throws XMLStreamException {
631
632        writer.writeStartElement(ELEM_PARALLEL);
633        writeTransitionTargetId(writer, parallel);
634
635        writeDatamodel(writer, parallel.getDatamodel());
636        writeHistory(writer, parallel.getHistory());
637        for (OnEntry onentry : parallel.getOnEntries()) {
638            writeOnEntry(writer, onentry);
639        }
640
641        for (Transition t : parallel.getTransitionsList()) {
642            writeTransition(writer, t);
643        }
644
645        for (Invoke inv : parallel.getInvokes()) {
646            writeInvoke(writer, inv);
647        }
648
649        for (EnterableState es : parallel.getChildren()) {
650            if (es instanceof Final) {
651                writeFinal(writer, (Final) es);
652            } else if (es instanceof State) {
653                writeState(writer, (State) es);
654            } else if (es instanceof Parallel) {
655                writeParallel(writer, (Parallel) es);
656            }
657        }
658
659        for (OnExit onexit : parallel.getOnExits()) {
660            writeOnExit(writer, onexit);
661        }
662        writer.writeEndElement();
663    }
664
665    /**
666     * Write out this {@link Final} object into its serialization as the corresponding &lt;final&gt; element.
667     *
668     * @param writer The {@link XMLStreamWriter} in use for the serialization.
669     * @param end The {@link Final} to serialize.
670     *
671     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
672     */
673    private static void writeFinal(final XMLStreamWriter writer, final Final end)
674            throws XMLStreamException {
675
676        writer.writeStartElement(ELEM_FINAL);
677        writeTransitionTargetId(writer, end);
678        for (OnEntry onentry : end.getOnEntries()) {
679            writeOnEntry(writer, onentry);
680        }
681        for (OnExit onexit : end.getOnExits()) {
682            writeOnExit(writer, onexit);
683        }
684        writer.writeEndElement();
685    }
686
687    /**
688     * Write out this {@link Initial} object into its serialization as the corresponding &lt;initial&gt; element.
689     *
690     * @param writer The {@link XMLStreamWriter} in use for the serialization.
691     * @param initial The {@link Initial} to serialize.
692     *
693     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
694     */
695    private static void writeInitial(final XMLStreamWriter writer, final Initial initial)
696            throws XMLStreamException {
697
698        if (initial == null || initial.isGenerated()) {
699            return;
700        }
701
702        writer.writeStartElement(ELEM_INITIAL);
703        writeTransition(writer, initial.getTransition());
704        writer.writeEndElement();
705    }
706
707    /**
708     * Write out this {@link History} list into its serialization as the corresponding set of &lt;history&gt;
709     * elements.
710     *
711     * @param writer The {@link XMLStreamWriter} in use for the serialization.
712     * @param history The {@link History} list to serialize.
713     *
714     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
715     */
716    private static void writeHistory(final XMLStreamWriter writer, final List<History> history)
717            throws XMLStreamException {
718
719        if (history == null) {
720            return;
721        }
722
723        for (History h : history) {
724            writer.writeStartElement(ELEM_HISTORY);
725            writeTransitionTargetId(writer, h);
726            if (h.isDeep()) {
727                writeAV(writer, ATTR_TYPE, "deep");
728            } else {
729                writeAV(writer, ATTR_TYPE, "shallow");
730            }
731            writeTransition(writer, h.getTransition());
732            writer.writeEndElement();
733        }
734    }
735
736    /**
737     * Write out this {@link OnEntry} object into its serialization as the corresponding &lt;onentry&gt; element.
738     *
739     * @param writer The {@link XMLStreamWriter} in use for the serialization.
740     * @param onentry The {@link OnEntry} to serialize.
741     *
742     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
743     */
744    private static void writeOnEntry(final XMLStreamWriter writer, final OnEntry onentry)
745            throws XMLStreamException {
746
747        if (onentry != null && (onentry.isRaiseEvent() || onentry.getActions().size() > 0 )) {
748            writer.writeStartElement(ELEM_ONENTRY);
749            writeAV(writer, ATTR_EVENT, onentry.getRaiseEvent());
750            writeExecutableContent(writer, onentry.getActions());
751            writer.writeEndElement();
752        }
753    }
754
755    /**
756     * Write out this {@link OnExit} object into its serialization as the corresponding &lt;onexit&gt; element.
757     *
758     * @param writer The {@link XMLStreamWriter} in use for the serialization.
759     * @param onexit The {@link OnExit} to serialize.
760     *
761     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
762     */
763    private static void writeOnExit(final XMLStreamWriter writer, final OnExit onexit)
764            throws XMLStreamException {
765
766        if (onexit != null && (onexit.isRaiseEvent() || onexit.getActions().size() > 0)) {
767            writer.writeStartElement(ELEM_ONEXIT);
768            writeAV(writer, ATTR_EVENT, onexit.getRaiseEvent());
769            writeExecutableContent(writer, onexit.getActions());
770            writer.writeEndElement();
771        }
772    }
773
774    /**
775     * Write out this {@link Transition} object into its serialization as the corresponding &lt;transition&gt; element.
776     *
777     * @param writer The {@link XMLStreamWriter} in use for the serialization.
778     * @param transition The {@link Transition} to serialize.
779     *
780     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
781     */
782    private static void writeTransition(final XMLStreamWriter writer, final SimpleTransition transition)
783            throws XMLStreamException {
784
785        writer.writeStartElement(ELEM_TRANSITION);
786        if (transition instanceof Transition) {
787            writeAV(writer, ATTR_EVENT, ((Transition)transition).getEvent());
788            writeAV(writer, ATTR_COND, escapeXML(((Transition)transition).getCond()));
789        }
790
791        writeAV(writer, ATTR_TARGET, transition.getNext());
792        if (transition.getType() != null) {
793            writeAV(writer, ATTR_TYPE, transition.getType().name());
794        }
795        writeExecutableContent(writer, transition.getActions());
796        writer.writeEndElement();
797    }
798
799    /**
800     * Write out this {@link Invoke} object into its serialization as the corresponding &lt;invoke&gt; element.
801     *
802     * @param writer The {@link XMLStreamWriter} in use for the serialization.
803     * @param invoke The {@link Invoke} to serialize.
804     *
805     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
806     */
807    private static void writeInvoke(final XMLStreamWriter writer, final Invoke invoke)
808            throws XMLStreamException {
809
810        writer.writeStartElement(ELEM_INVOKE);
811        writeAV(writer, ATTR_ID, invoke.getId());
812        writeAV(writer, ATTR_SRC, invoke.getSrc());
813        writeAV(writer, ATTR_SRCEXPR, invoke.getSrcexpr());
814        writeAV(writer, ATTR_TYPE, invoke.getType());
815        writeAV(writer, ATTR_AUTOFORWARD, invoke.getAutoForward());
816
817        for (Param p : invoke.getParams()) {
818            writer.writeStartElement(ELEM_PARAM);
819            writeAV(writer, ATTR_NAME, p.getName());
820            writeAV(writer, ATTR_LOCATION, p.getLocation());
821            writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr()));
822            writer.writeEndElement();
823        }
824        writeFinalize(writer, invoke.getFinalize());
825        writeContent(writer, invoke.getContent());
826
827        writer.writeEndElement();
828    }
829
830    /**
831     * Write out this {@link Finalize} object into its serialization as the corresponding &lt;finalize&gt; element.
832     *
833     * @param writer The {@link XMLStreamWriter} in use for the serialization.
834     * @param finalize The {@link Finalize} to serialize.
835     *
836     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
837     */
838    private static void writeFinalize(final XMLStreamWriter writer, final Finalize finalize)
839            throws XMLStreamException {
840
841        if (finalize != null && finalize.getActions().size() > 0) {
842            writer.writeStartElement(ELEM_FINALIZE);
843            writeExecutableContent(writer, finalize.getActions());
844            writer.writeEndElement();
845        }
846    }
847
848    /**
849     * Write out this executable content (list of actions) into its serialization as the corresponding set of action
850     * elements. Custom actions aren't serialized.
851     *
852     * @param writer The {@link XMLStreamWriter} in use for the serialization.
853     * @param actions The list of actions to serialize.
854     *
855     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
856     */
857    private static void writeExecutableContent(final XMLStreamWriter writer, final List<Action> actions)
858            throws XMLStreamException {
859
860        if (actions == null) {
861            return;
862        }
863        for (Action a : actions) {
864            if (a instanceof Assign) {
865                Assign asn = (Assign) a;
866                writer.writeStartElement(XMLNS_SCXML, ELEM_ASSIGN);
867                writeAV(writer, ATTR_LOCATION, asn.getLocation());
868                if (asn.getType() != null) {
869                    writeAV(writer, ATTR_TYPE, asn.getType().value());
870                }
871                writeAV(writer, ATTR_ATTR, asn.getAttr());
872                writeAV(writer, ATTR_SRC, asn.getSrc());
873                writeAV(writer, ATTR_EXPR, escapeXML(asn.getExpr()));
874                writer.writeEndElement();
875            } else if (a instanceof Send) {
876                writeSend(writer, (Send) a);
877            } else if (a instanceof Cancel) {
878                Cancel c = (Cancel) a;
879                writer.writeStartElement(XMLNS_SCXML, ELEM_CANCEL);
880                writeAV(writer, ATTR_SENDID, c.getSendid());
881                writer.writeEndElement();
882            } else if (a instanceof Foreach) {
883                writeForeach(writer, (Foreach) a);
884            } else if (a instanceof Log) {
885                Log lg = (Log) a;
886                writer.writeStartElement(XMLNS_SCXML, ELEM_LOG);
887                writeAV(writer, ATTR_LABEL, lg.getLabel());
888                writeAV(writer, ATTR_EXPR, escapeXML(lg.getExpr()));
889                writer.writeEndElement();
890            } else if (a instanceof Raise) {
891                Raise e = (Raise) a;
892                writer.writeStartElement(XMLNS_SCXML, ELEM_RAISE);
893                writeAV(writer, ATTR_EVENT, e.getEvent());
894                writer.writeEndElement();
895            } else if (a instanceof Script) {
896                Script s = (Script) a;
897                writer.writeStartElement(XMLNS_SCXML, ELEM_SCRIPT);
898                writer.writeCData(s.getScript());
899                writer.writeEndElement();
900            } else if (a instanceof If) {
901                writeIf(writer, (If) a);
902            } else if (a instanceof Else) {
903                writer.writeEmptyElement(ELEM_ELSE);
904            } else if (a instanceof ElseIf) {
905                ElseIf eif = (ElseIf) a;
906                writer.writeStartElement(XMLNS_SCXML, ELEM_ELSEIF);
907                writeAV(writer, ATTR_COND, escapeXML(eif.getCond()));
908                writer.writeEndElement();
909            } else if (a instanceof Var) {
910                Var v = (Var) a;
911                writer.writeStartElement(XMLNS_COMMONS_SCXML, ELEM_VAR);
912                writeAV(writer, ATTR_NAME, v.getName());
913                writeAV(writer, ATTR_EXPR, escapeXML(v.getExpr()));
914                writer.writeEndElement();
915            } else {
916                writer.writeComment("Custom action with class name '" + a.getClass().getName() + "' not serialized");
917            }
918        }
919    }
920
921    /**
922     * Write out this {@link Send} object into its serialization as the corresponding &lt;send&gt; element.
923     *
924     * @param writer The {@link XMLStreamWriter} in use for the serialization.
925     * @param send The {@link Send} to serialize.
926     *
927     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
928     */
929    private static void writeSend(final XMLStreamWriter writer, final Send send)
930            throws XMLStreamException {
931
932        writer.writeStartElement(XMLNS_SCXML, ELEM_SEND);
933        writeAV(writer, ATTR_ID, send.getId());
934        writeAV(writer, ATTR_IDLOCATION, send.getIdlocation());
935        writeAV(writer, ATTR_EVENT, send.getEvent());
936        writeAV(writer, ATTR_EVENTEXPR, send.getEventexpr());
937        writeAV(writer, ATTR_TARGET, send.getTarget());
938        writeAV(writer, ATTR_TARGETEXPR, send.getTargetexpr());
939        writeAV(writer, ATTR_TYPE, send.getType());
940        writeAV(writer, ATTR_TYPEEXPR, send.getTypeexpr());
941        writeAV(writer, ATTR_DELAY, send.getDelay());
942        writeAV(writer, ATTR_DELAYEXPR, send.getDelayexpr());
943        writeAV(writer, ATTR_NAMELIST, send.getNamelist());
944        writeAV(writer, ATTR_HINTS, send.getHints());
945
946        for (Param p : send.getParams()) {
947            writer.writeStartElement(ELEM_PARAM);
948            writeAV(writer, ATTR_NAME, p.getName());
949            writeAV(writer, ATTR_LOCATION, p.getLocation());
950            writeAV(writer, ATTR_EXPR, escapeXML(p.getExpr()));
951            writer.writeEndElement();
952        }
953        writeContent(writer, send.getContent());
954
955        writer.writeEndElement();
956    }
957
958    /**
959     * Write out this {@link If} object into its serialization as the corresponding &lt;if&gt; element.
960     *
961     * @param writer The {@link XMLStreamWriter} in use for the serialization.
962     * @param iff The {@link If} to serialize.
963     *
964     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
965     */
966    private static void writeIf(final XMLStreamWriter writer, final If iff)
967            throws XMLStreamException {
968
969        writer.writeStartElement(ELEM_IF);
970        writeAV(writer, ATTR_COND, escapeXML(iff.getCond()));
971        writeExecutableContent(writer, iff.getActions());
972        writer.writeEndElement();
973    }
974
975    /**
976     * Write out this {@link Foreach} object into its serialization as the corresponding &lt;foreach&gt; element.
977     *
978     * @param writer The {@link XMLStreamWriter} in use for the serialization.
979     * @param foreach The {@link If} to serialize.
980     *
981     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
982     */
983    private static void writeForeach(final XMLStreamWriter writer, final Foreach foreach)
984            throws XMLStreamException {
985
986        writer.writeStartElement(ELEM_FOREACH);
987        writeAV(writer, ATTR_ITEM, foreach.getItem());
988        writeAV(writer, ATTR_INDEX, foreach.getIndex());
989        writeAV(writer, ATTR_ARRAY, escapeXML(foreach.getArray()));
990        writeExecutableContent(writer, foreach.getActions());
991        writer.writeEndElement();
992    }
993
994    /**
995     * Write the {@link Content} element.
996     *
997     * @param writer The {@link XMLStreamWriter} in use for the serialization.
998     * @param content The content element to write.
999     *
1000     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1001     */
1002    private static void writeContent(final XMLStreamWriter writer, final Content content)
1003            throws XMLStreamException {
1004
1005        if (content != null) {
1006            writer.writeStartElement(ELEM_CONTENT);
1007            writeAV(writer, ATTR_EXPR, content.getExpr());
1008            if (content.getBody() != null) {
1009                if (content.getBody() instanceof Node) {
1010                    NodeList nodeList = ((Node)content.getBody()).getChildNodes();
1011                    if (nodeList.getLength() > 0 && XFORMER == null) {
1012                        writer.writeComment("External content was not serialized");
1013                    }
1014                    else {
1015                        for (int i = 0, size = nodeList.getLength(); i < size; i++) {
1016                            writeNode(writer, nodeList.item(i));
1017                        }
1018                    }
1019                }
1020                else {
1021                    writer.writeCharacters(content.getBody().toString());
1022                }
1023            }
1024            writer.writeEndElement();
1025        }
1026    }
1027
1028    /**
1029     * Write the serialized body of this {@link ExternalContent} element.
1030     *
1031     * @param writer The {@link XMLStreamWriter} in use for the serialization.
1032     * @param externalContent The model element containing the external body content.
1033     *
1034     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1035     */
1036    private static void writeExternalContent(final XMLStreamWriter writer,
1037                                             final ExternalContent externalContent)
1038            throws XMLStreamException {
1039
1040        List<Node> externalNodes = externalContent.getExternalNodes();
1041
1042        if (externalNodes.size() > 0 && XFORMER == null) {
1043            writer.writeComment("External content was not serialized");
1044        } else {
1045            for (Node n : externalNodes) {
1046                writeNode(writer, n);
1047            }
1048        }
1049    }
1050
1051    /**
1052     * Write out this {@link Node} object into its serialization.
1053     *
1054     * @param writer The {@link XMLStreamWriter} in use for the serialization.
1055     * @param node The {@link Node} to serialize.
1056     *
1057     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1058     */
1059    private static void writeNode(final XMLStreamWriter writer, final Node node)
1060            throws XMLStreamException {
1061
1062        Source input = new DOMSource(node);
1063        StringWriter out = new StringWriter();
1064        Result output = new StreamResult(out);
1065        try {
1066            XFORMER.transform(input, output);
1067        } catch (TransformerException te) {
1068            org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1069            log.error(te.getMessage(), te);
1070            writer.writeComment("TransformerException: Node was not serialized");
1071        }
1072        writer.writeCharacters(out.toString());
1073    }
1074
1075    /**
1076     * Write out this attribute, if the value is not <code>null</code>.
1077     *
1078     * @param writer The {@link XMLStreamWriter} in use for the serialization.
1079     * @param localName The local name of the attribute.
1080     * @param value The attribute value.
1081     *
1082     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1083     */
1084    private static void writeAV(final XMLStreamWriter writer, final String localName, final String value)
1085            throws XMLStreamException {
1086        if (value != null) {
1087            writer.writeAttribute(localName, value);
1088        }
1089    }
1090
1091    /**
1092     * Write out this attribute, if the value is not <code>null</code>.
1093     *
1094     * @param writer The {@link XMLStreamWriter} in use for the serialization.
1095     * @param localName The local name of the attribute.
1096     * @param value The attribute value.
1097     *
1098     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1099     */
1100    private static void writeAV(final XMLStreamWriter writer, final String localName, final Boolean value)
1101            throws XMLStreamException {
1102        if (value != null) {
1103            writer.writeAttribute(localName, value.toString());
1104        }
1105    }
1106
1107    /**
1108     * Write the serialized SCXML document while making attempts to make the serialization human readable. This
1109     * includes using new-lines and indentation as appropriate, where possible. Exactly one of the stream, writer
1110     * or result parameters must be provided.
1111     *
1112     * @param configuration The {@link Configuration} to use.
1113     * @param scxmlStream The optional {@link OutputStream} to write to.
1114     * @param scxmlWriter The optional {@link Writer} to write to.
1115     * @param scxmlResult The optional {@link Result} to write to.
1116     *
1117     * @throws IOException An IO error during serialization.
1118     * @throws XMLStreamException An exception processing the underlying {@link XMLStreamWriter}.
1119     */
1120    private static void writePretty(final Configuration configuration, final OutputStream scxmlStream,
1121                                    final Writer scxmlWriter, final Result scxmlResult)
1122            throws IOException, XMLStreamException {
1123
1124        // There isn't any portable way to write pretty using the JDK 1.6 StAX API
1125        configuration.internalWriter.flush();
1126        Source prettyPrintSource = new StreamSource(new StringReader(configuration.internalWriter.toString()));
1127        Result prettyPrintResult = null;
1128        if (scxmlStream != null) {
1129            prettyPrintResult = new StreamResult(scxmlStream);
1130        } else if (scxmlWriter != null) {
1131            prettyPrintResult = new StreamResult(scxmlWriter);
1132        } else if (scxmlResult != null) {
1133            prettyPrintResult = scxmlResult;
1134        }
1135
1136        TransformerFactory factory = TransformerFactory.newInstance();
1137        try {
1138            Transformer transformer = factory.newTransformer();
1139            if (configuration.encoding != null) {
1140                transformer.setOutputProperty(OutputKeys.ENCODING, configuration.encoding);
1141            }
1142            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1143            transformer.transform(prettyPrintSource, prettyPrintResult);
1144        } catch (TransformerException te) {
1145            throw new XMLStreamException("TransformerException while pretty printing SCXML", te);
1146        }
1147    }
1148
1149    /**
1150     * Use the supplied {@link Configuration} to create an appropriate {@link XMLStreamWriter} for this
1151     * {@link SCXMLWriter}. Exactly one of the stream, writer or result parameters must be provided.
1152     *
1153     * @param configuration The {@link Configuration} to use.
1154     * @param stream The optional {@link OutputStream} to write to.
1155     * @param writer The optional {@link Writer} to write to.
1156     * @param result The optional {@link Result} to write to.
1157     *
1158     * @return The appropriately configured {@link XMLStreamWriter}.
1159     *
1160     * @throws XMLStreamException A problem with the XML stream creation.
1161     */
1162    private static XMLStreamWriter getWriter(final Configuration configuration, final OutputStream stream,
1163                                             final Writer writer, final Result result)
1164            throws XMLStreamException {
1165
1166        // Instantiate the XMLOutputFactory
1167        XMLOutputFactory factory = XMLOutputFactory.newInstance();
1168        /*
1169        if (configuration.factoryId != null && configuration.factoryClassLoader != null) {
1170            // TODO StAX API bug means we can't use custom factories yet
1171            //factory = XMLOutputFactory.newInstance(configuration.factoryId, configuration.factoryClassLoader);
1172        }
1173        */
1174        for (Map.Entry<String, Object> property : configuration.properties.entrySet()) {
1175            factory.setProperty(property.getKey(), property.getValue());
1176        }
1177
1178        XMLStreamWriter xsw = null;
1179        if (configuration.usePrettyPrint || configuration.writeToString) {
1180            xsw = factory.createXMLStreamWriter(configuration.internalWriter);
1181        } else if (stream != null) {
1182            if (configuration.encoding != null) {
1183                xsw = factory.createXMLStreamWriter(stream, configuration.encoding);
1184            } else {
1185                xsw = factory.createXMLStreamWriter(stream);
1186            }
1187        } else if (writer != null) {
1188            xsw = factory.createXMLStreamWriter(writer);
1189        } else if (result != null) {
1190            xsw = factory.createXMLStreamWriter(result);
1191        }
1192        return xsw;
1193    }
1194
1195    /**
1196     * Get a {@link Transformer} instance that pretty prints the output.
1197     *
1198     * @return Transformer The indenting {@link Transformer} instance.
1199     */
1200    private static Transformer getTransformer() {
1201        Transformer transformer;
1202        Properties outputProps = new Properties();
1203        outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
1204        outputProps.put(OutputKeys.STANDALONE, "no");
1205        outputProps.put(OutputKeys.INDENT, "yes");
1206        try {
1207            TransformerFactory tfFactory = TransformerFactory.newInstance();
1208            transformer = tfFactory.newTransformer();
1209            transformer.setOutputProperties(outputProps);
1210        } catch (TransformerFactoryConfigurationError t) {
1211            org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1212            log.error(t.getMessage(), t);
1213            return null;
1214        } catch (TransformerConfigurationException e) {
1215            org.apache.commons.logging.Log log = LogFactory.getLog(SCXMLWriter.class);
1216            log.error(e.getMessage(), e);
1217            return null;
1218        }
1219        return transformer;
1220    }
1221
1222    /**
1223     * Discourage instantiation since this is a utility class.
1224     */
1225    private SCXMLWriter() {
1226        super();
1227    }
1228
1229    //------------------------- CONFIGURATION CLASS -------------------------//
1230    /**
1231     * <p>
1232     * Configuration for the {@link SCXMLWriter}. The configuration properties necessary for the following are
1233     * covered:
1234     * </p>
1235     *
1236     * <ul>
1237     *   <li>{@link XMLOutputFactory} configuration properties such as <code>factoryId</code> or any properties</li>
1238     *   <li>{@link XMLStreamWriter} configuration properties such as target {@link Writer} or {@link OutputStream}
1239     *   and the <code>encoding</code></li>
1240     * </ul>
1241     */
1242    public static class Configuration {
1243
1244        /*
1245         * Configuration properties for this {@link SCXMLWriter}.
1246         */
1247        // XMLOutputFactory configuration properties.
1248        /**
1249         * The <code>factoryId</code> to use for the {@link XMLOutputFactory}.
1250         */
1251        final String factoryId;
1252
1253        /**
1254         * The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to create.
1255         */
1256        final ClassLoader factoryClassLoader;
1257
1258        /**
1259         * The map of properties (keys are property name strings, values are object property values) for the
1260         * {@link XMLOutputFactory}.
1261         */
1262        final Map<String, Object> properties;
1263
1264        // XMLStreamWriter configuration properties.
1265        /**
1266         * The <code>encoding</code> to use for the {@link XMLStreamWriter}.
1267         */
1268        final String encoding;
1269
1270        /**
1271         * Whether to use a pretty print style that makes the output much more human readable.
1272         */
1273        final boolean usePrettyPrint;
1274
1275        /**
1276         * The intermediate writer that will hold the output to be pretty printed, given the lack of a standard
1277         * StAX property for the {@link XMLOutputFactory} in this regard. The contents will get transformed using
1278         * the transformation API.
1279         */
1280        final Writer internalWriter;
1281
1282        // Underlying stream or writer close
1283        /**
1284         * Whether to close the underlying stream or writer passed by the caller.
1285         */
1286        final boolean closeUnderlyingWhenDone;
1287
1288        /**
1289         * Whether to maintain an internal writer to return the serialization as a string.
1290         */
1291        boolean writeToString;
1292
1293        /**
1294         * The pretty print output as a string.
1295         */
1296        String prettyPrintOutput;
1297
1298        /*
1299         * Public constructors
1300         */
1301        /**
1302         * Default constructor.
1303         */
1304        public Configuration() {
1305
1306            this(null, null, null, null, false, false, false);
1307        }
1308
1309        /**
1310         * All-purpose constructor. Any of the parameters passed in can be <code>null</code> (booleans should default
1311         * to <code>false</code>). At the moment, the <code>factoryId</code> and <code>factoryClassLoader</code>
1312         * arguments are effectively ignored due to a bug in the underlying StAX {@link XMLOutputFactory} API.
1313         *
1314         * @param factoryId The <code>factoryId</code> to use.
1315         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to
1316         *                           create.
1317         * @param properties The map of properties (keys are property name strings, values are object property values)
1318         *                   for the {@link XMLOutputFactory}.
1319         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamWriter}
1320         * @param usePrettyPrint Whether to make the output human readable as far as possible. Since StAX does not
1321         *                       provide a portable way to do this in JDK 1.6, choosing the pretty print option
1322         *                       is currently not very efficient.
1323         * @param closeUnderlyingWhenDone Whether to close the underlying stream or writer passed by the caller.
1324         */
1325        public Configuration(final String factoryId, final ClassLoader factoryClassLoader,
1326                             final Map<String, Object> properties, final String encoding, final boolean usePrettyPrint,
1327                             final boolean closeUnderlyingWhenDone) {
1328
1329            this(factoryId, factoryClassLoader, properties, encoding, usePrettyPrint, closeUnderlyingWhenDone, false);
1330        }
1331
1332        /*
1333         * Package access constructors
1334         */
1335        /**
1336         * Convenience package access constructor.
1337         *
1338         * @param writeToString Whether we will be returning the serialization as a string.
1339         * @param usePrettyPrint Whether we will attempt to make the output human readable as far as possible.
1340         */
1341        Configuration(final boolean writeToString, final boolean usePrettyPrint) {
1342
1343            this(null, null, null, null, usePrettyPrint, false, writeToString);
1344        }
1345
1346        /**
1347         * All-purpose package access constructor.
1348         *
1349         * @param factoryId The <code>factoryId</code> to use.
1350         * @param factoryClassLoader The {@link ClassLoader} to use for the {@link XMLOutputFactory} instance to
1351         *                           create.
1352         * @param properties The map of properties (keys are property name strings, values are object property values)
1353         *                   for the {@link XMLOutputFactory}.
1354         * @param encoding The <code>encoding</code> to use for the {@link XMLStreamWriter}
1355         * @param usePrettyPrint Whether to make the output human readable as far as possible. Since StAX does not
1356         *                       provide a portable way to do this in JDK 1.6, choosing the pretty print option
1357         *                       is currently not very efficient.
1358         * @param closeUnderlyingWhenDone Whether to close the underlying stream or writer passed by the caller.
1359         * @param writeToString Whether to maintain an internal writer to return the serialization as a string.
1360         */
1361        Configuration(final String factoryId, final ClassLoader factoryClassLoader,
1362                      final Map<String, Object> properties, final String encoding, final boolean usePrettyPrint,
1363                      final boolean closeUnderlyingWhenDone, final boolean writeToString) {
1364
1365            this.factoryId = factoryId;
1366            this.factoryClassLoader = factoryClassLoader;
1367            this.properties = (properties == null ? new HashMap<String, Object>() : properties);
1368            this.encoding = encoding;
1369            this.usePrettyPrint = usePrettyPrint;
1370            this.closeUnderlyingWhenDone = closeUnderlyingWhenDone;
1371            this.writeToString = writeToString;
1372            if (this.usePrettyPrint || this.writeToString) {
1373                this.internalWriter = new StringWriter();
1374            } else {
1375                this.internalWriter = null;
1376            }
1377        }
1378    }
1379}