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 * "<i>cs</i>" prefix</li> 093 * <li>All namespace prefixes needed throughout the document are 094 * declared on the document root element (<scxml>)</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 <scxml> 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 <datamodel> 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 <state> 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 <parallel> 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 <final> 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 <initial> 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 <history> 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 <onentry> 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 <onexit> 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 <transition> 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 <invoke> 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 <finalize> 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 <send> 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 <if> 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 <foreach> 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}