1 /*
   2  * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.stream.writers;
  27 
  28 import java.io.FileOutputStream;
  29 import java.io.IOException;
  30 import java.io.OutputStream;
  31 import java.io.OutputStreamWriter;
  32 import java.io.Writer;
  33 import java.nio.charset.Charset;
  34 import java.nio.charset.CharsetEncoder;
  35 import java.util.AbstractMap;
  36 import java.util.ArrayList;
  37 import java.util.HashMap;
  38 import java.util.Iterator;
  39 import java.util.Random;
  40 import java.util.Vector;
  41 import java.util.Set;
  42 import java.util.Iterator;
  43 
  44 import javax.xml.XMLConstants;
  45 import javax.xml.namespace.NamespaceContext;
  46 import javax.xml.stream.XMLOutputFactory;
  47 import javax.xml.stream.XMLStreamConstants;
  48 import javax.xml.stream.XMLStreamException;
  49 import javax.xml.stream.XMLStreamWriter;
  50 import javax.xml.transform.stream.StreamResult;
  51 
  52 import com.sun.org.apache.xerces.internal.impl.Constants;
  53 import com.sun.org.apache.xerces.internal.impl.PropertyManager;
  54 import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
  55 import com.sun.org.apache.xerces.internal.util.SymbolTable;
  56 import com.sun.org.apache.xerces.internal.utils.SecuritySupport;
  57 import com.sun.org.apache.xerces.internal.xni.QName;
  58 
  59 import com.sun.xml.internal.stream.util.ReadOnlyIterator;
  60 
  61 /**
  62  * This class implements a StAX XMLStreamWriter. It extends
  63  * <code>AbstractMap</code> in order to support a getter for
  64  * implementation-specific properties. For example, you can get
  65  * the underlying <code>OutputStream</code> by casting an instance
  66  * of this class to <code>Map</code> and calling
  67  * <code>getProperty(OUTPUTSTREAM_PROPERTY)</code>.
  68  *
  69  * @author Neeraj Bajaj
  70  * @author K.Venugopal
  71  * @author Santiago.Pericas-Geertsen@sun.com
  72  * @author Sunitha.Reddy@sun.com
  73  */
  74 public final class XMLStreamWriterImpl extends AbstractMap implements XMLStreamWriter {
  75 
  76     public static final String START_COMMENT = "<!--";
  77     public static final String END_COMMENT = "-->";
  78     public static final String DEFAULT_ENCODING = " encoding=\"utf-8\"";
  79     public static final String DEFAULT_XMLDECL = "<?xml version=\"1.0\" ?>";
  80     public static final String DEFAULT_XML_VERSION = "1.0";
  81     public static final char CLOSE_START_TAG = '>';
  82     public static final char OPEN_START_TAG = '<';
  83     public static final String OPEN_END_TAG = "</";
  84     public static final char CLOSE_END_TAG = '>';
  85     public static final String START_CDATA = "<![CDATA[";
  86     public static final String END_CDATA = "]]>";
  87     public static final String CLOSE_EMPTY_ELEMENT = "/>";
  88     public static final String SPACE = " ";
  89     public static final String UTF_8 = "UTF-8";
  90 
  91     public static final String OUTPUTSTREAM_PROPERTY = "sjsxp-outputstream";
  92 
  93     /**
  94      * This flag can be used to turn escaping off for content. It does
  95      * not apply to attribute content.
  96      */
  97     boolean fEscapeCharacters = true;
  98 
  99     /**
 100      * Flag for the value of repairNamespace property
 101      */
 102     private boolean fIsRepairingNamespace = false;
 103 
 104     /**
 105      * Underlying Writer to which characters are written.
 106      */
 107     private Writer fWriter;
 108 
 109     /**
 110      * Underlying OutputStream to which <code>fWriter</code>
 111      * writes to. May be null if unknown.
 112      */
 113     private OutputStream fOutputStream = null;
 114 
 115     /**
 116      * Collects attributes when the writer is in reparing mode.
 117      */
 118     private ArrayList fAttributeCache;
 119 
 120     /**
 121      * Collects namespace declarations when the writer is in reparing mode.
 122      */
 123     private ArrayList fNamespaceDecls;
 124 
 125     /**
 126      * Namespace context encapsulating user specified context
 127      * and context built by the writer
 128      */
 129     private NamespaceContextImpl fNamespaceContext = null;
 130 
 131     private NamespaceSupport fInternalNamespaceContext = null;
 132 
 133     private Random fPrefixGen = null;
 134 
 135     /**
 136      * Reference to PropertyManager
 137      */
 138     private PropertyManager fPropertyManager = null;
 139 
 140     /**
 141      * Flag to track if start tag is opened
 142      */
 143     private boolean fStartTagOpened = false;
 144 
 145     /**
 146      * Boolean flag  to indicate, if instance can be reused
 147      */
 148     private boolean fReuse;
 149 
 150     private SymbolTable fSymbolTable = new SymbolTable();
 151 
 152     private ElementStack fElementStack = new ElementStack(); //Change this .-Venu
 153 
 154     final private String DEFAULT_PREFIX = fSymbolTable.addSymbol("");
 155 
 156     private final ReadOnlyIterator fReadOnlyIterator = new ReadOnlyIterator();
 157 
 158     /**
 159      * In some cases, this charset encoder is used to determine if a char is
 160      * encodable by underlying writer. For example, an 8-bit char from the
 161      * extended ASCII set is not encodable by 7-bit ASCII encoder. Unencodable
 162      * chars are escaped using XML numeric entities.
 163      */
 164     private CharsetEncoder fEncoder = null;
 165 
 166      /**
 167      * This is used to hold the namespace for attributes which happen to have
 168      * the same uri as the default namespace; It's added to avoid changing the
 169      * current impl. which has many redundant code for the repair mode
 170      */
 171     HashMap fAttrNamespace = null;
 172 
 173     /**
 174      * Creates a new instance of XMLStreamWriterImpl. Uses platform's default
 175      * encoding.
 176      *
 177      * @param outputStream Underlying stream to write the bytes to
 178      * @param props        Properties used by this writer
 179      */
 180     public XMLStreamWriterImpl(OutputStream outputStream, PropertyManager props)
 181         throws IOException {
 182 
 183         // cannot call this(outputStream, null, props); for constructor,
 184         // OutputStreamWriter charsetName cannot be null
 185 
 186         // use default encoding
 187         this(new OutputStreamWriter(outputStream), props);
 188     }
 189 
 190     /**
 191      * Creates a new instance of XMLStreamWriterImpl.
 192      *
 193      * @param outputStream Underlying stream to write the bytes
 194      * @param encoding     Encoding used to convert chars into bytes
 195      * @param props        Properties used by this writer
 196      */
 197     public XMLStreamWriterImpl(OutputStream outputStream, String encoding,
 198         PropertyManager props) throws java.io.IOException {
 199         this(new StreamResult(outputStream), encoding, props);
 200     }
 201 
 202     /**
 203      * Creates a new instance of XMLStreamWriterImpl using a Writer.
 204      *
 205      * @param writer  Underlying writer to which chars are written
 206      * @param props   Properties used by this writer
 207      */
 208     public XMLStreamWriterImpl(Writer writer, PropertyManager props)
 209         throws java.io.IOException {
 210         this(new StreamResult(writer), null, props);
 211     }
 212 
 213     /**
 214      * Creates a new instance of XMLStreamWriterImpl using a StreamResult.
 215      * A StreamResult encasupates an OutputStream, a Writer or a SystemId.
 216      *
 217      * @param writer  Underlying writer to which chars are written
 218      * @param props   Properties used by this writer
 219      */
 220     public XMLStreamWriterImpl(StreamResult sr, String encoding,
 221         PropertyManager props) throws java.io.IOException {
 222         setOutput(sr, encoding);
 223         fPropertyManager = props;
 224         init();
 225     }
 226 
 227     /**
 228      * Initialize an instance of this XMLStreamWriter. Allocate new instances
 229      * for all the data structures. Set internal flags based on property values.
 230      */
 231     private void init() {
 232         fReuse = false;
 233         fNamespaceDecls = new ArrayList();
 234         fPrefixGen = new Random();
 235         fAttributeCache = new ArrayList();
 236         fInternalNamespaceContext = new NamespaceSupport();
 237         fInternalNamespaceContext.reset();
 238         fNamespaceContext = new NamespaceContextImpl();
 239         fNamespaceContext.internalContext = fInternalNamespaceContext;
 240 
 241         // Set internal state based on property values
 242         Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
 243         fIsRepairingNamespace = ob.booleanValue();
 244         ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS);
 245         setEscapeCharacters(ob.booleanValue());
 246     }
 247 
 248     /**
 249      * Reset this instance so that it can be re-used. Do not read properties
 250      * again. The method <code>setOutput(StreamResult, encoding)</code> must
 251      * be called after this one.
 252      */
 253     public void reset() {
 254         reset(false);
 255     }
 256 
 257     /**
 258      * Reset this instance so that it can be re-used. Clears but does not
 259      * re-allocate internal data structures.
 260      *
 261      * @param resetProperties Indicates if properties should be read again
 262      */
 263     void reset(boolean resetProperties) {
 264         if (!fReuse) {
 265             throw new java.lang.IllegalStateException(
 266                 "close() Must be called before calling reset()");
 267         }
 268 
 269         fReuse = false;
 270         fNamespaceDecls.clear();
 271         fAttributeCache.clear();
 272 
 273         // reset Element/NamespaceContext stacks
 274         fElementStack.clear();
 275         fInternalNamespaceContext.reset();
 276 
 277         fStartTagOpened = false;
 278         fNamespaceContext.userContext = null;
 279 
 280         if (resetProperties) {
 281             Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
 282             fIsRepairingNamespace = ob.booleanValue();
 283             ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS);
 284             setEscapeCharacters(ob.booleanValue());
 285         }
 286     }
 287 
 288     /**
 289      * Use a StreamResult to initialize the output for this XMLStreamWriter. Check
 290      * for OutputStream, Writer and then systemId, in that order.
 291      *
 292      * @param sr        StreamResult encapsulating output information
 293      * @param encoding  Encoding to be used except when a Writer is available
 294      */
 295     public void setOutput(StreamResult sr, String encoding)
 296         throws IOException {
 297 
 298         if (sr.getOutputStream() != null) {
 299             setOutputUsingStream(sr.getOutputStream(), encoding);
 300         }
 301         else if (sr.getWriter() != null) {
 302             setOutputUsingWriter(sr.getWriter());
 303         }
 304         else if (sr.getSystemId() != null) {
 305             setOutputUsingStream(new FileOutputStream(sr.getSystemId()),
 306                 encoding);
 307         }
 308     }
 309 
 310      private void setOutputUsingWriter(Writer writer)
 311         throws IOException
 312      {
 313          fWriter = writer;
 314 
 315          if (writer instanceof OutputStreamWriter) {
 316              String charset = ((OutputStreamWriter) writer).getEncoding();
 317              if (charset != null && !charset.equalsIgnoreCase("utf-8")) {
 318                  fEncoder = Charset.forName(charset).newEncoder();
 319              }
 320          }
 321      }
 322 
 323     /**
 324      * Utility method to create a writer when passed an OutputStream. Make
 325      * sure to wrap an <code>OutputStreamWriter</code> using an
 326      * <code>XMLWriter</code> for performance reasons.
 327      *
 328      * @param os        Underlying OutputStream
 329      * @param encoding  Encoding used to convert chars into bytes
 330      */
 331     private void setOutputUsingStream(OutputStream os, String encoding)
 332         throws IOException {
 333         fOutputStream = os;
 334 
 335         if (encoding != null) {
 336             if (encoding.equalsIgnoreCase("utf-8")) {
 337                 fWriter = new UTF8OutputStreamWriter(os);
 338             }
 339             else {
 340                 fWriter = new XMLWriter(new OutputStreamWriter(os, encoding));
 341                 fEncoder = Charset.forName(encoding).newEncoder();
 342             }
 343         } else {
 344             encoding = SecuritySupport.getSystemProperty("file.encoding");
 345             if (encoding != null && encoding.equalsIgnoreCase("utf-8")) {
 346                 fWriter = new UTF8OutputStreamWriter(os);
 347             } else {
 348                 fWriter = new XMLWriter(new OutputStreamWriter(os));
 349             }
 350         }
 351     }
 352 
 353     /** Can this instance be reused
 354      *
 355      * @return boolean boolean value to indicate if this instance can be reused or not
 356      */
 357     public boolean canReuse() {
 358         return fReuse;
 359     }
 360 
 361     public void setEscapeCharacters(boolean escape) {
 362         fEscapeCharacters = escape;
 363     }
 364 
 365     public boolean getEscapeCharacters() {
 366         return fEscapeCharacters;
 367     }
 368 
 369     /**
 370      * Close this XMLStreamWriter by closing underlying writer.
 371      */
 372     public void close() throws XMLStreamException {
 373         if (fWriter != null) {
 374             try {
 375                 //fWriter.close();
 376                 fWriter.flush();
 377             } catch (IOException e) {
 378                 throw new XMLStreamException(e);
 379             }
 380         }
 381         fWriter = null;
 382         fOutputStream = null;
 383         fNamespaceDecls.clear();
 384         fAttributeCache.clear();
 385         fElementStack.clear();
 386         fInternalNamespaceContext.reset();
 387         fReuse = true;
 388         fStartTagOpened = false;
 389         fNamespaceContext.userContext = null;
 390     }
 391 
 392     /**
 393      * Flush this XMLStreamWriter by flushin underlying writer.
 394      */
 395     public void flush() throws XMLStreamException {
 396         try {
 397             fWriter.flush();
 398         } catch (IOException e) {
 399             throw new XMLStreamException(e);
 400         }
 401     }
 402 
 403     /**
 404      * Return <code>NamespaceContext</code> being used by the writer.
 405      *
 406      * @return NamespaceContext
 407      */
 408     public NamespaceContext getNamespaceContext() {
 409         return fNamespaceContext;
 410     }
 411 
 412     /**
 413      * Return a prefix associated with specified uri, or null if the
 414      * uri is unknown.
 415      *
 416      * @param  uri The namespace uri
 417      * @throws XMLStreamException if uri specified is "" or null
 418      */
 419     public String getPrefix(String uri) throws XMLStreamException {
 420         return fNamespaceContext.getPrefix(uri);
 421     }
 422 
 423     /**
 424      * Returns value associated with the specified property name.
 425      *
 426      * @param  str Property name
 427      * @throws IllegalArgumentException if the specified property is not supported
 428      * @return value associated with the specified property.
 429      */
 430     public Object getProperty(String str)
 431         throws IllegalArgumentException {
 432         if (str == null) {
 433             throw new NullPointerException();
 434         }
 435 
 436         if (!fPropertyManager.containsProperty(str)) {
 437             throw new IllegalArgumentException("Property '" + str +
 438                 "' is not supported");
 439         }
 440 
 441         return fPropertyManager.getProperty(str);
 442     }
 443 
 444     /**
 445      * Set the specified URI as default namespace in the current namespace context.
 446      *
 447      * @param uri Namespace URI
 448      */
 449     public void setDefaultNamespace(String uri) throws XMLStreamException {
 450         if (uri != null) {
 451             uri = fSymbolTable.addSymbol(uri);
 452         }
 453 
 454         if (fIsRepairingNamespace) {
 455             if (isDefaultNamespace(uri)) {
 456                 return;
 457             }
 458 
 459             QName qname = new QName();
 460             qname.setValues(DEFAULT_PREFIX, "xmlns", null, uri);
 461             fNamespaceDecls.add(qname);
 462         } else {
 463             fInternalNamespaceContext.declarePrefix(DEFAULT_PREFIX, uri);
 464         }
 465     }
 466 
 467     /**
 468      * Sets the current <code>NamespaceContext</code> for prefix and uri bindings.
 469      * This context becomes the root namespace context for writing and
 470      * will replace the current root namespace context. Subsequent calls
 471      * to setPrefix and setDefaultNamespace will bind namespaces using
 472      * the context passed to the method as the root context for resolving
 473      * namespaces. This method may only be called once at the start of the
 474      * document. It does not cause the namespaces to be declared. If a
 475      * namespace URI to prefix mapping is found in the namespace context
 476      * it is treated as declared and the prefix may be used by the
 477      * <code>XMLStreamWriter</code>.
 478      *
 479      * @param namespaceContext the namespace context to use for this writer, may not be null
 480      * @throws XMLStreamException
 481      */
 482     public void setNamespaceContext(NamespaceContext namespaceContext)
 483         throws XMLStreamException {
 484         fNamespaceContext.userContext = namespaceContext;
 485     }
 486 
 487     /**
 488      * Sets the prefix the uri is bound to. This prefix is bound in the scope of
 489      * the current START_ELEMENT / END_ELEMENT pair. If this method is called before
 490      * a START_ELEMENT has been written the prefix is bound in the root scope.
 491      *
 492      * @param prefix
 493      * @param uri
 494      * @throws XMLStreamException
 495      */
 496     public void setPrefix(String prefix, String uri) throws XMLStreamException {
 497 
 498         if (prefix == null) {
 499             throw new XMLStreamException("Prefix cannot be null");
 500         }
 501 
 502         if (uri == null) {
 503             throw new XMLStreamException("URI cannot be null");
 504         }
 505 
 506         prefix = fSymbolTable.addSymbol(prefix);
 507         uri = fSymbolTable.addSymbol(uri);
 508 
 509         if (fIsRepairingNamespace) {
 510             String tmpURI = fInternalNamespaceContext.getURI(prefix);
 511 
 512             if ((tmpURI != null) && (tmpURI == uri)) {
 513                 return;
 514             }
 515 
 516             if(checkUserNamespaceContext(prefix,uri))
 517                 return;
 518             QName qname = new QName();
 519             qname.setValues(prefix,XMLConstants.XMLNS_ATTRIBUTE, null,uri);
 520             fNamespaceDecls.add(qname);
 521 
 522             return;
 523         }
 524 
 525         fInternalNamespaceContext.declarePrefix(prefix, uri);
 526     }
 527 
 528     public void writeAttribute(String localName, String value)
 529         throws XMLStreamException {
 530         try {
 531             if (!fStartTagOpened) {
 532                 throw new XMLStreamException(
 533                     "Attribute not associated with any element");
 534             }
 535 
 536             if (fIsRepairingNamespace) {
 537                 Attribute attr = new Attribute(value); // Revisit:Dont create new one's. Reuse.-Venu
 538                 attr.setValues(null, localName, null, null);
 539                 fAttributeCache.add(attr);
 540 
 541                 return;
 542             }
 543 
 544             fWriter.write(" ");
 545             fWriter.write(localName);
 546             fWriter.write("=\"");
 547             writeXMLContent(
 548                     value,
 549                     true,   // true = escapeChars
 550                     true);  // true = escapeDoubleQuotes
 551             fWriter.write("\"");
 552         } catch (IOException e) {
 553             throw new XMLStreamException(e);
 554         }
 555     }
 556 
 557     public void writeAttribute(String namespaceURI, String localName,
 558         String value) throws XMLStreamException {
 559         try {
 560             if (!fStartTagOpened) {
 561                 throw new XMLStreamException(
 562                     "Attribute not associated with any element");
 563             }
 564 
 565             if (namespaceURI == null) {
 566                 throw new XMLStreamException("NamespaceURI cannot be null");
 567             }
 568 
 569             namespaceURI = fSymbolTable.addSymbol(namespaceURI);
 570 
 571             String prefix = fInternalNamespaceContext.getPrefix(namespaceURI);
 572 
 573             if (!fIsRepairingNamespace) {
 574                 if (prefix == null) {
 575                     throw new XMLStreamException("Prefix cannot be null");
 576                 }
 577 
 578                 writeAttributeWithPrefix(prefix, localName, value);
 579             } else {
 580                 Attribute attr = new Attribute(value);
 581                 attr.setValues(null, localName, null, namespaceURI);
 582                 fAttributeCache.add(attr);
 583             }
 584         } catch (IOException e) {
 585             throw new XMLStreamException(e);
 586         }
 587     }
 588 
 589     private void writeAttributeWithPrefix(String prefix, String localName,
 590         String value) throws IOException {
 591         fWriter.write(SPACE);
 592 
 593         if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
 594             fWriter.write(prefix);
 595             fWriter.write(":");
 596         }
 597 
 598         fWriter.write(localName);
 599         fWriter.write("=\"");
 600         writeXMLContent(value,
 601                 true,   // true = escapeChars
 602                 true);  // true = escapeDoubleQuotes
 603         fWriter.write("\"");
 604     }
 605 
 606     public void writeAttribute(String prefix, String namespaceURI,
 607         String localName, String value) throws XMLStreamException {
 608         try {
 609             if (!fStartTagOpened) {
 610                 throw new XMLStreamException(
 611                     "Attribute not associated with any element");
 612             }
 613 
 614             if (namespaceURI == null) {
 615                 throw new XMLStreamException("NamespaceURI cannot be null");
 616             }
 617 
 618             if (localName == null) {
 619                 throw new XMLStreamException("Local name cannot be null");
 620             }
 621 
 622             if (!fIsRepairingNamespace) {
 623                 if (prefix == null || prefix.equals("")){
 624                     if (!namespaceURI.equals("")) {
 625                         throw new XMLStreamException("prefix cannot be null or empty");
 626                     } else {
 627                         writeAttributeWithPrefix(null, localName, value);
 628                         return;
 629                     }
 630                 }
 631 
 632                 if (!prefix.equals(XMLConstants.XML_NS_PREFIX) || !namespaceURI.equals(XMLConstants.XML_NS_URI)) {
 633 
 634                     prefix = fSymbolTable.addSymbol(prefix);
 635                     namespaceURI = fSymbolTable.addSymbol(namespaceURI);
 636 
 637                     if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){
 638 
 639                         String tmpURI = fInternalNamespaceContext.getURI(prefix);
 640 
 641                         if (tmpURI != null && tmpURI != namespaceURI){
 642                             throw new XMLStreamException("Prefix "+prefix+" is " +
 643                                     "already bound to "+tmpURI+
 644                                     ". Trying to rebind it to "+namespaceURI+" is an error.");
 645                         }
 646                     }
 647                     fInternalNamespaceContext.declarePrefix(prefix, namespaceURI);
 648                 }
 649                 writeAttributeWithPrefix(prefix, localName, value);
 650             } else {
 651                 if (prefix != null) {
 652                     prefix = fSymbolTable.addSymbol(prefix);
 653                 }
 654 
 655                 namespaceURI = fSymbolTable.addSymbol(namespaceURI);
 656 
 657                 Attribute attr = new Attribute(value);
 658                 attr.setValues(prefix, localName, null, namespaceURI);
 659                 fAttributeCache.add(attr);
 660             }
 661         } catch (IOException e) {
 662             throw new XMLStreamException(e);
 663         }
 664     }
 665 
 666     public void writeCData(String cdata) throws XMLStreamException {
 667         try {
 668             if (cdata == null) {
 669                 throw new XMLStreamException("cdata cannot be null");
 670             }
 671 
 672             if (fStartTagOpened) {
 673                 closeStartTag();
 674             }
 675 
 676             fWriter.write(START_CDATA);
 677             fWriter.write(cdata);
 678             fWriter.write(END_CDATA);
 679         } catch (IOException e) {
 680             throw new XMLStreamException(e);
 681         }
 682     }
 683 
 684     public void writeCharacters(String data) throws XMLStreamException {
 685         try {
 686             if (fStartTagOpened) {
 687                 closeStartTag();
 688             }
 689 
 690             writeXMLContent(data);
 691         } catch (IOException e) {
 692             throw new XMLStreamException(e);
 693         }
 694     }
 695 
 696     public void writeCharacters(char[] data, int start, int len)
 697         throws XMLStreamException {
 698         try {
 699             if (fStartTagOpened) {
 700                 closeStartTag();
 701             }
 702 
 703             writeXMLContent(data, start, len, fEscapeCharacters);
 704         } catch (IOException e) {
 705             throw new XMLStreamException(e);
 706         }
 707     }
 708 
 709     public void writeComment(String comment) throws XMLStreamException {
 710         try {
 711             if (fStartTagOpened) {
 712                 closeStartTag();
 713             }
 714 
 715             fWriter.write(START_COMMENT);
 716 
 717             if (comment != null) {
 718                 fWriter.write(comment);
 719             }
 720 
 721             fWriter.write(END_COMMENT);
 722         } catch (IOException e) {
 723             throw new XMLStreamException(e);
 724         }
 725     }
 726 
 727     public void writeDTD(String dtd) throws XMLStreamException {
 728         try {
 729             if (fStartTagOpened) {
 730                 closeStartTag();
 731             }
 732 
 733             fWriter.write(dtd);
 734         } catch (IOException e) {
 735             throw new XMLStreamException(e);
 736         }
 737     }
 738 
 739     /*
 740      * Write default Namespace.
 741      *
 742      * If namespaceURI == null,
 743      * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI},
 744      * i.e. there is no Namespace.
 745      *
 746      * @param namespaceURI NamespaceURI to declare.
 747      *
 748      * @throws XMLStreamException
 749      *
 750      * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting">
 751      *   Namespaces in XML, 5.2 Namespace Defaulting</a>
 752      */
 753     public void writeDefaultNamespace(String namespaceURI)
 754         throws XMLStreamException {
 755 
 756         // normalize namespaceURI
 757         String namespaceURINormalized = null;
 758         if (namespaceURI == null) {
 759             namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI
 760         } else {
 761             namespaceURINormalized = namespaceURI;
 762         }
 763 
 764         try {
 765             if (!fStartTagOpened) {
 766                 throw new IllegalStateException(
 767                     "Namespace Attribute not associated with any element");
 768             }
 769 
 770             if (fIsRepairingNamespace) {
 771                 QName qname = new QName();
 772                 qname.setValues(XMLConstants.DEFAULT_NS_PREFIX,
 773                     XMLConstants.XMLNS_ATTRIBUTE, null, namespaceURINormalized);
 774                 fNamespaceDecls.add(qname);
 775 
 776                 return;
 777             }
 778 
 779             namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized);
 780 
 781             if (fInternalNamespaceContext.containsPrefixInCurrentContext("")){
 782 
 783                 String tmp = fInternalNamespaceContext.getURI("");
 784 
 785                 if (tmp != null && tmp != namespaceURINormalized) {
 786                         throw new XMLStreamException(
 787                                 "xmlns has been already bound to " +tmp +
 788                                 ". Rebinding it to "+ namespaceURINormalized +
 789                                 " is an error");
 790                     }
 791             }
 792             fInternalNamespaceContext.declarePrefix("", namespaceURINormalized);
 793 
 794             // use common namespace code with a prefix == null for xmlns="..."
 795             writenamespace(null, namespaceURINormalized);
 796         } catch (IOException e) {
 797             throw new XMLStreamException(e);
 798         }
 799     }
 800 
 801     public void writeEmptyElement(String localName) throws XMLStreamException {
 802         try {
 803             if (fStartTagOpened) {
 804                 closeStartTag();
 805             }
 806 
 807             openStartTag();
 808             fElementStack.push(null, localName, null, null, true);
 809             fInternalNamespaceContext.pushContext();
 810 
 811             if (!fIsRepairingNamespace) {
 812                 fWriter.write(localName);
 813             }
 814         } catch (IOException e) {
 815             throw new XMLStreamException(e);
 816         }
 817     }
 818 
 819     public void writeEmptyElement(String namespaceURI, String localName)
 820         throws XMLStreamException {
 821         if (namespaceURI == null) {
 822             throw new XMLStreamException("NamespaceURI cannot be null");
 823         }
 824 
 825         namespaceURI = fSymbolTable.addSymbol(namespaceURI);
 826 
 827         String prefix = fNamespaceContext.getPrefix(namespaceURI);
 828         writeEmptyElement(prefix, localName, namespaceURI);
 829     }
 830 
 831     public void writeEmptyElement(String prefix, String localName,
 832         String namespaceURI) throws XMLStreamException {
 833         try {
 834             if (localName == null) {
 835                 throw new XMLStreamException("Local Name cannot be null");
 836             }
 837 
 838             if (namespaceURI == null) {
 839                 throw new XMLStreamException("NamespaceURI cannot be null");
 840             }
 841 
 842             if (prefix != null) {
 843                 prefix = fSymbolTable.addSymbol(prefix);
 844             }
 845 
 846             namespaceURI = fSymbolTable.addSymbol(namespaceURI);
 847 
 848             if (fStartTagOpened) {
 849                 closeStartTag();
 850             }
 851 
 852             openStartTag();
 853 
 854             fElementStack.push(prefix, localName, null, namespaceURI, true);
 855             fInternalNamespaceContext.pushContext();
 856 
 857             if (!fIsRepairingNamespace) {
 858                 if (prefix == null) {
 859                     throw new XMLStreamException("NamespaceURI " +
 860                         namespaceURI + " has not been bound to any prefix");
 861                 }
 862             } else {
 863                 return;
 864             }
 865 
 866             if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
 867                 fWriter.write(prefix);
 868                 fWriter.write(":");
 869             }
 870 
 871             fWriter.write(localName);
 872         } catch (IOException e) {
 873             throw new XMLStreamException(e);
 874         }
 875     }
 876 
 877     public void writeEndDocument() throws XMLStreamException {
 878         try {
 879             if (fStartTagOpened) {
 880                 closeStartTag();
 881             }
 882 
 883             ElementState elem = null;
 884 
 885             while (!fElementStack.empty()) {
 886                 elem = (ElementState) fElementStack.pop();
 887                 fInternalNamespaceContext.popContext();
 888 
 889                 if (elem.isEmpty) {
 890                     //fWriter.write(CLOSE_EMPTY_ELEMENT);
 891                 } else {
 892                     fWriter.write(OPEN_END_TAG);
 893 
 894                     if ((elem.prefix != null) && !(elem.prefix).equals("")) {
 895                         fWriter.write(elem.prefix);
 896                         fWriter.write(":");
 897                     }
 898 
 899                     fWriter.write(elem.localpart);
 900                     fWriter.write(CLOSE_END_TAG);
 901                 }
 902             }
 903         } catch (IOException e) {
 904             throw new XMLStreamException(e);
 905         } catch (ArrayIndexOutOfBoundsException e) {
 906             throw new XMLStreamException("No more elements to write");
 907         }
 908     }
 909 
 910     public void writeEndElement() throws XMLStreamException {
 911         try {
 912             if (fStartTagOpened) {
 913                 closeStartTag();
 914             }
 915 
 916             ElementState currentElement = (ElementState) fElementStack.pop();
 917 
 918             if (currentElement == null) {
 919                 throw new XMLStreamException("No element was found to write");
 920             }
 921 
 922             if (currentElement.isEmpty) {
 923                 //fWriter.write(CLOSE_EMPTY_ELEMENT);
 924                 return;
 925             }
 926 
 927             fWriter.write(OPEN_END_TAG);
 928 
 929             if ((currentElement.prefix != null) &&
 930                     !(currentElement.prefix).equals("")) {
 931                 fWriter.write(currentElement.prefix);
 932                 fWriter.write(":");
 933             }
 934 
 935             fWriter.write(currentElement.localpart);
 936             fWriter.write(CLOSE_END_TAG);
 937             fInternalNamespaceContext.popContext();
 938         } catch (IOException e) {
 939             throw new XMLStreamException(e);
 940         } catch (ArrayIndexOutOfBoundsException e) {
 941             throw new XMLStreamException(
 942                     "No element was found to write: "
 943                     + e.toString(), e);
 944         }
 945     }
 946 
 947     public void writeEntityRef(String refName) throws XMLStreamException {
 948         try {
 949             if (fStartTagOpened) {
 950                 closeStartTag();
 951             }
 952 
 953             fWriter.write('&');
 954             fWriter.write(refName);
 955             fWriter.write(';');
 956         } catch (IOException e) {
 957             throw new XMLStreamException(e);
 958         }
 959     }
 960 
 961     /**
 962      * Write a Namespace declaration.
 963      *
 964      * If namespaceURI == null,
 965      * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI},
 966      * i.e. there is no Namespace.
 967      *
 968      * @param prefix Prefix to bind.
 969      * @param namespaceURI NamespaceURI to declare.
 970      *
 971      * @throws XMLStreamException
 972      *
 973      * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting">
 974      *   Namespaces in XML, 5.2 Namespace Defaulting</a>
 975      */
 976     public void writeNamespace(String prefix, String namespaceURI)
 977         throws XMLStreamException {
 978 
 979         // normalize namespaceURI
 980         String namespaceURINormalized = null;
 981         if (namespaceURI == null) {
 982             namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI
 983         } else {
 984             namespaceURINormalized = namespaceURI;
 985         }
 986 
 987         try {
 988             QName qname = null;
 989 
 990             if (!fStartTagOpened) {
 991                 throw new IllegalStateException(
 992                         "Invalid state: start tag is not opened at writeNamespace("
 993                         + prefix
 994                         + ", "
 995                         + namespaceURINormalized
 996                         + ")");
 997             }
 998 
 999             // is this the default Namespace?
1000             if (prefix == null
1001                     || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)
1002                     || prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
1003                 writeDefaultNamespace(namespaceURINormalized);
1004                 return;
1005             }
1006 
1007             if (prefix.equals(XMLConstants.XML_NS_PREFIX) && namespaceURINormalized.equals(XMLConstants.XML_NS_URI))
1008                 return;
1009 
1010             prefix = fSymbolTable.addSymbol(prefix);
1011             namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized);
1012 
1013             if (fIsRepairingNamespace) {
1014                 String tmpURI = fInternalNamespaceContext.getURI(prefix);
1015 
1016                 if ((tmpURI != null) && (tmpURI == namespaceURINormalized)) {
1017                     return;
1018                 }
1019 
1020                 qname = new QName();
1021                 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null,
1022                     namespaceURINormalized);
1023                 fNamespaceDecls.add(qname);
1024 
1025                 return;
1026             }
1027 
1028 
1029             if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){
1030 
1031                 String tmp = fInternalNamespaceContext.getURI(prefix);
1032 
1033                 if (tmp != null && tmp != namespaceURINormalized) {
1034 
1035                        throw new XMLStreamException("prefix "+prefix+
1036                             " has been already bound to " +tmp +
1037                             ". Rebinding it to "+ namespaceURINormalized+
1038                             " is an error");
1039                 }
1040             }
1041 
1042             fInternalNamespaceContext.declarePrefix(prefix, namespaceURINormalized);
1043             writenamespace(prefix, namespaceURINormalized);
1044 
1045         } catch (IOException e) {
1046             throw new XMLStreamException(e);
1047         }
1048     }
1049 
1050     private void writenamespace(String prefix, String namespaceURI)
1051         throws IOException {
1052         fWriter.write(" xmlns");
1053 
1054         if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1055             fWriter.write(":");
1056             fWriter.write(prefix);
1057         }
1058 
1059         fWriter.write("=\"");
1060         writeXMLContent(
1061                 namespaceURI,
1062                 true,   // true = escapeChars
1063                 true);  // true = escapeDoubleQuotes
1064         fWriter.write("\"");
1065     }
1066 
1067     public void writeProcessingInstruction(String target)
1068         throws XMLStreamException {
1069         try {
1070             if (fStartTagOpened) {
1071                 closeStartTag();
1072             }
1073 
1074             if (target != null) {
1075                 fWriter.write("<?");
1076                 fWriter.write(target);
1077                 fWriter.write("?>");
1078 
1079                 return;
1080             }
1081         } catch (IOException e) {
1082             throw new XMLStreamException(e);
1083         }
1084 
1085         throw new XMLStreamException("PI target cannot be null");
1086     }
1087 
1088     /**
1089      * @param target
1090      * @param data
1091      * @throws XMLStreamException
1092      */
1093     public void writeProcessingInstruction(String target, String data)
1094         throws XMLStreamException {
1095         try {
1096             if (fStartTagOpened) {
1097                 closeStartTag();
1098             }
1099 
1100             if ((target == null) || (data == null)) {
1101                 throw new XMLStreamException("PI target cannot be null");
1102             }
1103 
1104             fWriter.write("<?");
1105             fWriter.write(target);
1106             fWriter.write(SPACE);
1107             fWriter.write(data);
1108             fWriter.write("?>");
1109         } catch (IOException e) {
1110             throw new XMLStreamException(e);
1111         }
1112     }
1113 
1114     /**
1115      * @throws XMLStreamException
1116      */
1117     public void writeStartDocument() throws XMLStreamException {
1118         try {
1119             fWriter.write(DEFAULT_XMLDECL);
1120         } catch (IOException ex) {
1121             throw new XMLStreamException(ex);
1122         }
1123     }
1124 
1125     /**
1126      * @param version
1127      * @throws XMLStreamException
1128      */
1129     public void writeStartDocument(String version) throws XMLStreamException {
1130         try {
1131             if ((version == null) || version.equals("")) {
1132                 writeStartDocument();
1133 
1134                 return;
1135             }
1136 
1137             fWriter.write("<?xml version=\"");
1138             fWriter.write(version);
1139             fWriter.write("\"");
1140 
1141             //fWriter.write(DEFAULT_ENCODING);
1142             fWriter.write("?>");
1143         } catch (IOException ex) {
1144             throw new XMLStreamException(ex);
1145         }
1146     }
1147 
1148     /**
1149      * @param encoding
1150      * @param version
1151      * @throws XMLStreamException
1152      */
1153     public void writeStartDocument(String encoding, String version)
1154         throws XMLStreamException {
1155         //Revisit : What about standalone ?
1156         try {
1157             if ((encoding == null) && (version == null)) {
1158                 writeStartDocument();
1159 
1160                 return;
1161             }
1162 
1163             if (encoding == null) {
1164                 writeStartDocument(version);
1165 
1166                 return;
1167             }
1168 
1169             String streamEncoding = null;
1170             if (fWriter instanceof OutputStreamWriter) {
1171                 streamEncoding = ((OutputStreamWriter) fWriter).getEncoding();
1172             }
1173             else if (fWriter instanceof UTF8OutputStreamWriter) {
1174                 streamEncoding = ((UTF8OutputStreamWriter) fWriter).getEncoding();
1175             }
1176             else if (fWriter instanceof XMLWriter) {
1177                 streamEncoding = ((OutputStreamWriter) ((XMLWriter)fWriter).getWriter()).getEncoding();
1178             }
1179 
1180             if (streamEncoding != null && !streamEncoding.equalsIgnoreCase(encoding)) {
1181                 // If the equality check failed, check for charset encoding aliases
1182                 boolean foundAlias = false;
1183                 Set aliases = Charset.forName(encoding).aliases();
1184                 for (Iterator it = aliases.iterator(); !foundAlias && it.hasNext(); ) {
1185                     if (streamEncoding.equalsIgnoreCase((String) it.next())) {
1186                         foundAlias = true;
1187                     }
1188                 }
1189                 // If no alias matches the encoding name, then report error
1190                 if (!foundAlias) {
1191                     throw new XMLStreamException("Underlying stream encoding '"
1192                             + streamEncoding
1193                             + "' and input paramter for writeStartDocument() method '"
1194                             + encoding + "' do not match.");
1195                 }
1196             }
1197 
1198 
1199             fWriter.write("<?xml version=\"");
1200 
1201             if ((version == null) || version.equals("")) {
1202                 fWriter.write(DEFAULT_XML_VERSION);
1203             } else {
1204                 fWriter.write(version);
1205             }
1206 
1207             if (!encoding.equals("")) {
1208                 fWriter.write("\" encoding=\"");
1209                 fWriter.write(encoding);
1210             }
1211 
1212             fWriter.write("\"?>");
1213         } catch (IOException ex) {
1214             throw new XMLStreamException(ex);
1215         }
1216     }
1217 
1218     /**
1219      * @param localName
1220      * @throws XMLStreamException
1221      */
1222     public void writeStartElement(String localName) throws XMLStreamException {
1223         try {
1224             if (localName == null) {
1225                 throw new XMLStreamException("Local Name cannot be null");
1226             }
1227 
1228             if (fStartTagOpened) {
1229                 closeStartTag();
1230             }
1231 
1232             openStartTag();
1233             fElementStack.push(null, localName, null, null, false);
1234             fInternalNamespaceContext.pushContext();
1235 
1236             if (fIsRepairingNamespace) {
1237                 return;
1238             }
1239 
1240             fWriter.write(localName);
1241         } catch (IOException ex) {
1242             throw new XMLStreamException(ex);
1243         }
1244     }
1245 
1246     /**
1247      * @param namespaceURI
1248      * @param localName
1249      * @throws XMLStreamException
1250      */
1251     public void writeStartElement(String namespaceURI, String localName)
1252         throws XMLStreamException {
1253         if (localName == null) {
1254             throw new XMLStreamException("Local Name cannot be null");
1255         }
1256 
1257         if (namespaceURI == null) {
1258             throw new XMLStreamException("NamespaceURI cannot be null");
1259         }
1260 
1261         namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1262 
1263         String prefix = null;
1264 
1265         if (!fIsRepairingNamespace) {
1266             prefix = fNamespaceContext.getPrefix(namespaceURI);
1267 
1268             if (prefix != null) {
1269                 prefix = fSymbolTable.addSymbol(prefix);
1270             }
1271         }
1272 
1273         writeStartElement(prefix, localName, namespaceURI);
1274     }
1275 
1276     /**
1277      * @param prefix
1278      * @param localName
1279      * @param namespaceURI
1280      * @throws XMLStreamException
1281      */
1282     public void writeStartElement(String prefix, String localName,
1283         String namespaceURI) throws XMLStreamException {
1284         try {
1285             if (localName == null) {
1286                 throw new XMLStreamException("Local Name cannot be null");
1287             }
1288 
1289             if (namespaceURI == null) {
1290                 throw new XMLStreamException("NamespaceURI cannot be null");
1291             }
1292 
1293             if (!fIsRepairingNamespace) {
1294                 if (prefix == null) {
1295                     throw new XMLStreamException("Prefix cannot be null");
1296                 }
1297             }
1298 
1299             if (fStartTagOpened) {
1300                 closeStartTag();
1301             }
1302 
1303             openStartTag();
1304             namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1305 
1306             if (prefix != null) {
1307                 prefix = fSymbolTable.addSymbol(prefix);
1308             }
1309 
1310             fElementStack.push(prefix, localName, null, namespaceURI, false);
1311             fInternalNamespaceContext.pushContext();
1312 
1313             String tmpPrefix = fNamespaceContext.getPrefix(namespaceURI);
1314 
1315 
1316             if ((prefix != null) &&
1317                     ((tmpPrefix == null) || !prefix.equals(tmpPrefix))) {
1318                 fInternalNamespaceContext.declarePrefix(prefix, namespaceURI);
1319 
1320             }
1321 
1322             if (fIsRepairingNamespace) {
1323                 if ((prefix == null) ||
1324                         ((tmpPrefix != null) && prefix.equals(tmpPrefix))) {
1325                     return;
1326                 }
1327 
1328                 QName qname = new QName();
1329                 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null,
1330                     namespaceURI);
1331                 fNamespaceDecls.add(qname);
1332 
1333                 return;
1334             }
1335 
1336             if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1337                 fWriter.write(prefix);
1338                 fWriter.write(":");
1339             }
1340 
1341             fWriter.write(localName);
1342 
1343         } catch (IOException ex) {
1344             throw new XMLStreamException(ex);
1345         }
1346     }
1347 
1348     /**
1349      * Writes character reference in hex format.
1350      */
1351     private void writeCharRef(int codePoint) throws IOException {
1352         fWriter.write( "&#x" );
1353         fWriter.write( Integer.toHexString(codePoint) );
1354         fWriter.write( ';' );
1355     }
1356 
1357     /**
1358      * Writes XML content to underlying writer. Escapes characters unless
1359      * escaping character feature is turned off.
1360      */
1361     private void writeXMLContent(char[] content, int start, int length,
1362         boolean escapeChars) throws IOException {
1363         if (!escapeChars) {
1364             fWriter.write(content, start, length);
1365 
1366             return;
1367         }
1368 
1369         // Index of the next char to be written
1370         int startWritePos = start;
1371 
1372         final int end = start + length;
1373 
1374         for (int index = start; index < end; index++) {
1375             char ch = content[index];
1376 
1377             if (fEncoder != null && !fEncoder.canEncode(ch)){
1378                 fWriter.write(content, startWritePos, index - startWritePos );
1379 
1380                 // Check if current and next characters forms a surrogate pair
1381                 // and escape it to avoid generation of invalid xml content
1382                 if ( index != end - 1 && Character.isSurrogatePair(ch, content[index+1])) {
1383                     writeCharRef(Character.toCodePoint(ch, content[index+1]));
1384                     index++;
1385                 } else {
1386                     writeCharRef(ch);
1387                 }
1388                 startWritePos = index + 1;
1389                 continue;
1390             }
1391 
1392             switch (ch) {
1393             case '<':
1394                 fWriter.write(content, startWritePos, index - startWritePos);
1395                 fWriter.write("&lt;");
1396                 startWritePos = index + 1;
1397 
1398                 break;
1399 
1400             case '&':
1401                 fWriter.write(content, startWritePos, index - startWritePos);
1402                 fWriter.write("&amp;");
1403                 startWritePos = index + 1;
1404 
1405                 break;
1406 
1407             case '>':
1408                 fWriter.write(content, startWritePos, index - startWritePos);
1409                 fWriter.write("&gt;");
1410                 startWritePos = index + 1;
1411 
1412                 break;
1413             }
1414         }
1415 
1416         // Write any pending data
1417         fWriter.write(content, startWritePos, end - startWritePos);
1418     }
1419 
1420     private void writeXMLContent(String content) throws IOException {
1421         if ((content != null) && (content.length() > 0)) {
1422             writeXMLContent(content,
1423                     fEscapeCharacters,  // boolean = escapeChars
1424                     false);             // false = escapeDoubleQuotes
1425         }
1426     }
1427 
1428     /**
1429      * Writes XML content to underlying writer. Escapes characters unless
1430      * escaping character feature is turned off.
1431      */
1432     private void writeXMLContent(
1433             String content,
1434             boolean escapeChars,
1435             boolean escapeDoubleQuotes)
1436         throws IOException {
1437 
1438         if (!escapeChars) {
1439             fWriter.write(content);
1440 
1441             return;
1442         }
1443 
1444         // Index of the next char to be written
1445         int startWritePos = 0;
1446 
1447         final int end = content.length();
1448 
1449         for (int index = 0; index < end; index++) {
1450             char ch = content.charAt(index);
1451 
1452             if (fEncoder != null && !fEncoder.canEncode(ch)){
1453                 fWriter.write(content, startWritePos, index - startWritePos );
1454 
1455                 // Check if current and next characters forms a surrogate pair
1456                 // and escape it to avoid generation of invalid xml content
1457                 if ( index != end - 1 && Character.isSurrogatePair(ch, content.charAt(index+1))) {
1458                     writeCharRef(Character.toCodePoint(ch, content.charAt(index+1)));
1459                     index++;
1460                 } else {
1461                     writeCharRef(ch);
1462                 }
1463 
1464                 startWritePos = index + 1;
1465                 continue;
1466             }
1467 
1468             switch (ch) {
1469             case '<':
1470                 fWriter.write(content, startWritePos, index - startWritePos);
1471                 fWriter.write("&lt;");
1472                 startWritePos = index + 1;
1473 
1474                 break;
1475 
1476             case '&':
1477                 fWriter.write(content, startWritePos, index - startWritePos);
1478                 fWriter.write("&amp;");
1479                 startWritePos = index + 1;
1480 
1481                 break;
1482 
1483             case '>':
1484                 fWriter.write(content, startWritePos, index - startWritePos);
1485                 fWriter.write("&gt;");
1486                 startWritePos = index + 1;
1487 
1488                 break;
1489 
1490             case '"':
1491                 fWriter.write(content, startWritePos, index - startWritePos);
1492                 if (escapeDoubleQuotes) {
1493                     fWriter.write("&quot;");
1494                 } else {
1495                     fWriter.write('"');
1496                 }
1497                 startWritePos = index + 1;
1498 
1499                 break;
1500             }
1501         }
1502 
1503         // Write any pending data
1504         fWriter.write(content, startWritePos, end - startWritePos);
1505     }
1506 
1507     /**
1508      * marks close of start tag and writes the same into the writer.
1509      */
1510     private void closeStartTag() throws XMLStreamException {
1511         try {
1512             ElementState currentElement = fElementStack.peek();
1513 
1514             if (fIsRepairingNamespace) {
1515                 repair();
1516                 correctPrefix(currentElement, XMLStreamConstants.START_ELEMENT);
1517 
1518                 if ((currentElement.prefix != null) &&
1519                         (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1520                     fWriter.write(currentElement.prefix);
1521                     fWriter.write(":");
1522                 }
1523 
1524                 fWriter.write(currentElement.localpart);
1525 
1526                 int len = fNamespaceDecls.size();
1527                 QName qname = null;
1528 
1529                 for (int i = 0; i < len; i++) {
1530                     qname = (QName) fNamespaceDecls.get(i);
1531 
1532                     if (qname != null) {
1533                         if (fInternalNamespaceContext.declarePrefix(qname.prefix,
1534                             qname.uri)) {
1535                             writenamespace(qname.prefix, qname.uri);
1536                         }
1537                     }
1538                 }
1539 
1540                 fNamespaceDecls.clear();
1541 
1542                 Attribute attr = null;
1543 
1544                 for (int j = 0; j < fAttributeCache.size(); j++) {
1545                     attr = (Attribute) fAttributeCache.get(j);
1546 
1547                     if ((attr.prefix != null) && (attr.uri != null)) {
1548                         if (!attr.prefix.equals("") && !attr.uri.equals("") ) {
1549                             String tmp = fInternalNamespaceContext.getPrefix(attr.uri);
1550 
1551                             if ((tmp == null) || (tmp != attr.prefix)) {
1552                                 tmp = getAttrPrefix(attr.uri);
1553                                 if (tmp == null) {
1554                                     if (fInternalNamespaceContext.declarePrefix(attr.prefix,
1555                                         attr.uri)) {
1556                                         writenamespace(attr.prefix, attr.uri);
1557                                     }
1558                                 } else {
1559                                     writenamespace(attr.prefix, attr.uri);
1560                                 }
1561                             }
1562                         }
1563                     }
1564 
1565                     writeAttributeWithPrefix(attr.prefix, attr.localpart,
1566                         attr.value);
1567                 }
1568                 fAttrNamespace = null;
1569                 fAttributeCache.clear();
1570             }
1571 
1572             if (currentElement.isEmpty) {
1573                 fElementStack.pop();
1574                 fInternalNamespaceContext.popContext();
1575                 fWriter.write(CLOSE_EMPTY_ELEMENT);
1576             } else {
1577                 fWriter.write(CLOSE_START_TAG);
1578             }
1579 
1580             fStartTagOpened = false;
1581         } catch (IOException ex) {
1582             fStartTagOpened = false;
1583             throw new XMLStreamException(ex);
1584         }
1585     }
1586 
1587     /**
1588      * marks open of start tag and writes the same into the writer.
1589      */
1590     private void openStartTag() throws IOException {
1591         fStartTagOpened = true;
1592         fWriter.write(OPEN_START_TAG);
1593     }
1594 
1595     /**
1596      *
1597      * @param uri
1598      * @return
1599      */
1600     private void correctPrefix(QName attr, int type) {
1601         String tmpPrefix = null;
1602         String prefix;
1603         String uri;
1604         prefix = attr.prefix;
1605         uri = attr.uri;
1606         boolean isSpecialCaseURI = false;
1607 
1608         if (prefix == null || prefix.equals("")) {
1609             if (uri == null) {
1610                 return;
1611             }
1612 
1613             if (prefix == XMLConstants.DEFAULT_NS_PREFIX && uri == XMLConstants.DEFAULT_NS_PREFIX)
1614                 return;
1615 
1616             uri = fSymbolTable.addSymbol(uri);
1617 
1618             QName decl = null;
1619 
1620             for (int i = 0; i < fNamespaceDecls.size(); i++) {
1621                 decl = (QName) fNamespaceDecls.get(i);
1622 
1623                 if ((decl != null) && (decl.uri == attr.uri)) {
1624                     attr.prefix = decl.prefix;
1625 
1626                     return;
1627                 }
1628             }
1629 
1630             tmpPrefix = fNamespaceContext.getPrefix(uri);
1631 
1632             if (tmpPrefix == XMLConstants.DEFAULT_NS_PREFIX) {
1633                 if (type == XMLStreamConstants.START_ELEMENT) {
1634                     return;
1635                 }
1636                 else if (type == XMLStreamConstants.ATTRIBUTE) {
1637                     //the uri happens to be the same as that of the default namespace
1638                     tmpPrefix = getAttrPrefix(uri);
1639                     isSpecialCaseURI = true;
1640                 }
1641             }
1642 
1643             if (tmpPrefix == null) {
1644                 StringBuffer genPrefix = new StringBuffer("zdef");
1645 
1646                 for (int i = 0; i < 1; i++) {
1647                     genPrefix.append(fPrefixGen.nextInt());
1648                 }
1649 
1650                 prefix = genPrefix.toString();
1651                 prefix = fSymbolTable.addSymbol(prefix);
1652             } else {
1653                 prefix = fSymbolTable.addSymbol(tmpPrefix);
1654             }
1655 
1656             if (tmpPrefix == null) {
1657                 if (isSpecialCaseURI) {
1658                     addAttrNamespace(prefix, uri);
1659                 } else {
1660                     QName qname = new QName();
1661                     qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, uri);
1662                     fNamespaceDecls.add(qname);
1663                     fInternalNamespaceContext.declarePrefix(fSymbolTable.addSymbol(
1664                         prefix), uri);
1665                 }
1666             }
1667         }
1668 
1669         attr.prefix = prefix;
1670     }
1671 
1672     /**
1673      * return the prefix if the attribute has an uri the same as that of the default namespace
1674      */
1675     private String getAttrPrefix(String uri) {
1676         if (fAttrNamespace != null) {
1677             return (String)fAttrNamespace.get(uri);
1678         }
1679         return null;
1680     }
1681     private void addAttrNamespace(String prefix, String uri) {
1682         if (fAttrNamespace == null) {
1683             fAttrNamespace = new HashMap();
1684         }
1685         fAttrNamespace.put(prefix, uri);
1686     }
1687     /**
1688      * @param uri
1689      * @return
1690      */
1691     private boolean isDefaultNamespace(String uri) {
1692         String defaultNamespace = fInternalNamespaceContext.getURI(DEFAULT_PREFIX);
1693 
1694         if (uri == defaultNamespace) {
1695             return true;
1696         }
1697 
1698         return false;
1699     }
1700 
1701     /**
1702      * @param prefix
1703      * @param uri
1704      * @return
1705      */
1706     private boolean checkUserNamespaceContext(String prefix, String uri) {
1707         if (fNamespaceContext.userContext != null) {
1708             String tmpURI = fNamespaceContext.userContext.getNamespaceURI(prefix);
1709 
1710             if ((tmpURI != null) && tmpURI.equals(uri)) {
1711                 return true;
1712             }
1713         }
1714 
1715         return false;
1716     }
1717 
1718     /**
1719      * Correct's namespaces  as per requirements of isReparisingNamespace property.
1720      */
1721     protected void repair() {
1722         Attribute attr = null;
1723         Attribute attr2 = null;
1724         ElementState currentElement = fElementStack.peek();
1725         removeDuplicateDecls();
1726 
1727         for(int i=0 ; i< fAttributeCache.size();i++){
1728             attr = (Attribute)fAttributeCache.get(i);
1729             if((attr.prefix != null && !attr.prefix.equals("")) || (attr.uri != null && !attr.uri.equals(""))) {
1730                 correctPrefix(currentElement,attr);
1731             }
1732         }
1733 
1734         if (!isDeclared(currentElement)) {
1735             if ((currentElement.prefix != null) &&
1736                     (currentElement.uri != null)) {
1737                 if ((!currentElement.prefix.equals("")) && (!currentElement.uri.equals(""))) {
1738                     fNamespaceDecls.add(currentElement);
1739                 }
1740             }
1741         }
1742 
1743         for(int i=0 ; i< fAttributeCache.size();i++){
1744             attr = (Attribute)fAttributeCache.get(i);
1745             for(int j=i+1;j<fAttributeCache.size();j++){
1746                 attr2 = (Attribute)fAttributeCache.get(j);
1747                 if(!"".equals(attr.prefix)&& !"".equals(attr2.prefix)){
1748                     correctPrefix(attr,attr2);
1749                 }
1750             }
1751         }
1752 
1753         repairNamespaceDecl(currentElement);
1754 
1755         int i = 0;
1756 
1757         for (i = 0; i < fAttributeCache.size(); i++) {
1758             attr = (Attribute) fAttributeCache.get(i);
1759             /* If 'attr' is an attribute and it is in no namespace(which means that prefix="", uri=""), attr's
1760                namespace should not be redinded. See [http://www.w3.org/TR/REC-xml-names/#defaulting].
1761              */
1762             if (attr.prefix != null && attr.prefix.equals("") && attr.uri != null && attr.uri.equals("")){
1763                 repairNamespaceDecl(attr);
1764             }
1765         }
1766 
1767         QName qname = null;
1768 
1769         for (i = 0; i < fNamespaceDecls.size(); i++) {
1770             qname = (QName) fNamespaceDecls.get(i);
1771 
1772             if (qname != null) {
1773                 fInternalNamespaceContext.declarePrefix(qname.prefix, qname.uri);
1774             }
1775         }
1776 
1777         for (i = 0; i < fAttributeCache.size(); i++) {
1778             attr = (Attribute) fAttributeCache.get(i);
1779             correctPrefix(attr, XMLStreamConstants.ATTRIBUTE);
1780         }
1781     }
1782 
1783     /*
1784      *If element and/or attribute names in the same start or empty-element tag
1785      *are bound to different namespace URIs and are using the same prefix then
1786      *the element or the first occurring attribute retains the original prefix
1787      *and the following attributes have their prefixes replaced with a new prefix
1788      *that is bound to the namespace URIs of those attributes.
1789      */
1790     void correctPrefix(QName attr1, QName attr2) {
1791         String tmpPrefix = null;
1792         QName decl = null;
1793         boolean done = false;
1794 
1795         checkForNull(attr1);
1796         checkForNull(attr2);
1797 
1798         if(attr1.prefix.equals(attr2.prefix) && !(attr1.uri.equals(attr2.uri))){
1799 
1800             tmpPrefix = fNamespaceContext.getPrefix(attr2.uri);
1801 
1802             if (tmpPrefix != null) {
1803                 attr2.prefix = fSymbolTable.addSymbol(tmpPrefix);
1804             } else {
1805                 decl = null;
1806                 for(int n=0;n<fNamespaceDecls.size();n++){
1807                     decl = (QName)fNamespaceDecls.get(n);
1808                     if(decl != null && (decl.uri == attr2.uri)){
1809                         attr2.prefix = decl.prefix;
1810 
1811                         return;
1812                     }
1813                 }
1814 
1815                 //No namespace mapping found , so declare prefix.
1816                 StringBuffer genPrefix = new StringBuffer("zdef");
1817 
1818                 for (int k = 0; k < 1; k++) {
1819                     genPrefix.append(fPrefixGen.nextInt());
1820                 }
1821 
1822                 tmpPrefix = genPrefix.toString();
1823                 tmpPrefix = fSymbolTable.addSymbol(tmpPrefix);
1824                 attr2.prefix = tmpPrefix;
1825 
1826                 QName qname = new QName();
1827                 qname.setValues(tmpPrefix, XMLConstants.XMLNS_ATTRIBUTE, null,
1828                     attr2.uri);
1829                 fNamespaceDecls.add(qname);
1830             }
1831         }
1832     }
1833 
1834     void checkForNull(QName attr) {
1835         if (attr.prefix == null) attr.prefix = XMLConstants.DEFAULT_NS_PREFIX;
1836         if (attr.uri == null) attr.uri = XMLConstants.DEFAULT_NS_PREFIX;
1837     }
1838 
1839     void removeDuplicateDecls(){
1840         QName decl1,decl2;
1841         for(int i =0;i<fNamespaceDecls.size();i++){
1842             decl1 = (QName)fNamespaceDecls.get(i);
1843             if(decl1!=null) {
1844                 for(int j=i+1;j<fNamespaceDecls.size();j++){
1845                     decl2 = (QName)fNamespaceDecls.get(j);
1846                     // QName.equals relies on identity equality, so we can't use it,
1847                     // because prefixes aren't interned
1848                     if(decl2!=null && decl1.prefix.equals(decl2.prefix) && decl1.uri.equals(decl2.uri))
1849                         fNamespaceDecls.remove(j);
1850                 }
1851             }
1852         }
1853     }
1854 
1855     /*
1856      *If an element or attribute name is bound to a prefix and there is a namespace
1857      *declaration that binds that prefix to a different URI then that namespace declaration
1858      *is either removed if the correct mapping is inherited from the parent context of that element,
1859      *or changed to the namespace URI of the element or attribute using that prefix.
1860      *
1861      */
1862     void repairNamespaceDecl(QName attr) {
1863         QName decl = null;
1864         String tmpURI;
1865 
1866         //check for null prefix.
1867         for (int j = 0; j < fNamespaceDecls.size(); j++) {
1868             decl = (QName) fNamespaceDecls.get(j);
1869 
1870             if (decl != null) {
1871                 if ((attr.prefix != null) &&
1872                         (attr.prefix.equals(decl.prefix) &&
1873                         !(attr.uri.equals(decl.uri)))) {
1874                     tmpURI = fNamespaceContext.getNamespaceURI(attr.prefix);
1875 
1876                     //see if you need to add to symbole table.
1877                     if (tmpURI != null) {
1878                         if (tmpURI.equals(attr.uri)) {
1879                             fNamespaceDecls.set(j, null);
1880                         } else {
1881                             decl.uri = attr.uri;
1882                         }
1883                     }
1884                 }
1885             }
1886         }
1887     }
1888 
1889     boolean isDeclared(QName attr) {
1890         QName decl = null;
1891 
1892         for (int n = 0; n < fNamespaceDecls.size(); n++) {
1893             decl = (QName) fNamespaceDecls.get(n);
1894 
1895             if ((attr.prefix != null) &&
1896                     ((attr.prefix == decl.prefix) && (decl.uri == attr.uri))) {
1897                 return true;
1898             }
1899         }
1900 
1901         if (attr.uri != null) {
1902             if (fNamespaceContext.getPrefix(attr.uri) != null) {
1903                 return true;
1904             }
1905         }
1906 
1907         return false;
1908     }
1909 
1910     /*
1911      * Start of Internal classes.
1912      *
1913      */
1914     protected class ElementStack {
1915         /** The stack data. */
1916         protected ElementState[] fElements;
1917 
1918         /** The size of the stack. */
1919         protected short fDepth;
1920 
1921         /** Default constructor. */
1922         public ElementStack() {
1923             fElements = new ElementState[10];
1924 
1925             for (int i = 0; i < fElements.length; i++) {
1926                 fElements[i] = new ElementState();
1927             }
1928         }
1929 
1930         /**
1931          * Pushes an element on the stack.
1932          * <p>
1933          * <strong>Note:</strong> The QName values are copied into the
1934          * stack. In other words, the caller does <em>not</em> orphan
1935          * the element to the stack. Also, the QName object returned
1936          * is <em>not</em> orphaned to the caller. It should be
1937          * considered read-only.
1938          *
1939          * @param element The element to push onto the stack.
1940          *
1941          * @return Returns the actual QName object that stores the
1942          */
1943         public ElementState push(ElementState element) {
1944             if (fDepth == fElements.length) {
1945                 ElementState[] array = new ElementState[fElements.length * 2];
1946                 System.arraycopy(fElements, 0, array, 0, fDepth);
1947                 fElements = array;
1948 
1949                 for (int i = fDepth; i < fElements.length; i++) {
1950                     fElements[i] = new ElementState();
1951                 }
1952             }
1953 
1954             fElements[fDepth].setValues(element);
1955 
1956             return fElements[fDepth++];
1957         }
1958 
1959         /**
1960          *
1961          * @param prefix
1962          * @param localpart
1963          * @param rawname
1964          * @param uri
1965          * @param isEmpty
1966          * @return
1967          */
1968         public ElementState push(String prefix, String localpart,
1969             String rawname, String uri, boolean isEmpty) {
1970             if (fDepth == fElements.length) {
1971                 ElementState[] array = new ElementState[fElements.length * 2];
1972                 System.arraycopy(fElements, 0, array, 0, fDepth);
1973                 fElements = array;
1974 
1975                 for (int i = fDepth; i < fElements.length; i++) {
1976                     fElements[i] = new ElementState();
1977                 }
1978             }
1979 
1980             fElements[fDepth].setValues(prefix, localpart, rawname, uri, isEmpty);
1981 
1982             return fElements[fDepth++];
1983         }
1984 
1985         /**
1986          * Pops an element off of the stack by setting the values of
1987          * the specified QName.
1988          * <p>
1989          * <strong>Note:</strong> The object returned is <em>not</em>
1990          * orphaned to the caller. Therefore, the caller should consider
1991          * the object to be read-only.
1992          */
1993         public ElementState pop() {
1994             return fElements[--fDepth];
1995         }
1996 
1997         /** Clears the stack without throwing away existing QName objects. */
1998         public void clear() {
1999             fDepth = 0;
2000         }
2001 
2002         /**
2003          * This function is as a result of optimization done for endElement --
2004          * we dont need to set the value for every end element we encouter.
2005          * For Well formedness checks we can have the same QName object that was pushed.
2006          * the values will be set only if application need to know about the endElement
2007          * -- neeraj.bajaj@sun.com
2008          */
2009         public ElementState peek() {
2010             return fElements[fDepth - 1];
2011         }
2012 
2013         /**
2014          *
2015          * @return
2016          */
2017         public boolean empty() {
2018             return (fDepth > 0) ? false : true;
2019         }
2020     }
2021 
2022     /**
2023      * Maintains element state . localName for now.
2024      */
2025     class ElementState extends QName {
2026         public boolean isEmpty = false;
2027 
2028         public ElementState() {}
2029 
2030         public ElementState(String prefix, String localpart, String rawname,
2031             String uri) {
2032             super(prefix, localpart, rawname, uri);
2033         }
2034 
2035         public void setValues(String prefix, String localpart, String rawname,
2036             String uri, boolean isEmpty) {
2037             super.setValues(prefix, localpart, rawname, uri);
2038             this.isEmpty = isEmpty;
2039         }
2040     }
2041 
2042     /**
2043      * Attributes
2044      */
2045     class Attribute extends QName {
2046         String value;
2047 
2048         Attribute(String value) {
2049             super();
2050             this.value = value;
2051         }
2052     }
2053 
2054     /**
2055      * Implementation of NamespaceContext .
2056      *
2057      */
2058     class NamespaceContextImpl implements NamespaceContext {
2059         //root namespace context set by user.
2060         NamespaceContext userContext = null;
2061 
2062         //context built by the writer.
2063         NamespaceSupport internalContext = null;
2064 
2065         public String getNamespaceURI(String prefix) {
2066             String uri = null;
2067 
2068             if (prefix != null) {
2069                 prefix = fSymbolTable.addSymbol(prefix);
2070             }
2071 
2072             if (internalContext != null) {
2073                 uri = internalContext.getURI(prefix);
2074 
2075                 if (uri != null) {
2076                     return uri;
2077                 }
2078             }
2079 
2080             if (userContext != null) {
2081                 uri = userContext.getNamespaceURI(prefix);
2082 
2083                 return uri;
2084             }
2085 
2086             return null;
2087         }
2088 
2089         public String getPrefix(String uri) {
2090             String prefix = null;
2091 
2092             if (uri != null) {
2093                 uri = fSymbolTable.addSymbol(uri);
2094             }
2095 
2096             if (internalContext != null) {
2097                 prefix = internalContext.getPrefix(uri);
2098 
2099                 if (prefix != null) {
2100                     return prefix;
2101                 }
2102             }
2103 
2104             if (userContext != null) {
2105                 return userContext.getPrefix(uri);
2106             }
2107 
2108             return null;
2109         }
2110 
2111         public java.util.Iterator getPrefixes(String uri) {
2112             Vector prefixes = null;
2113             Iterator itr = null;
2114 
2115             if (uri != null) {
2116                 uri = fSymbolTable.addSymbol(uri);
2117             }
2118 
2119             if (userContext != null) {
2120                 itr = userContext.getPrefixes(uri);
2121             }
2122 
2123             if (internalContext != null) {
2124                 prefixes = internalContext.getPrefixes(uri);
2125             }
2126 
2127             if ((prefixes == null) && (itr != null)) {
2128                 return itr;
2129             } else if ((prefixes != null) && (itr == null)) {
2130                 return new ReadOnlyIterator(prefixes.iterator());
2131             } else if ((prefixes != null) && (itr != null)) {
2132                 String ob = null;
2133 
2134                 while (itr.hasNext()) {
2135                     ob = (String) itr.next();
2136 
2137                     if (ob != null) {
2138                         ob = fSymbolTable.addSymbol(ob);
2139                     }
2140 
2141                     if (!prefixes.contains(ob)) {
2142                         prefixes.add(ob);
2143                     }
2144                 }
2145 
2146                 return new ReadOnlyIterator(prefixes.iterator());
2147             }
2148 
2149             return fReadOnlyIterator;
2150         }
2151     }
2152 
2153     // -- Map Interface --------------------------------------------------
2154 
2155     public int size() {
2156         return 1;
2157     }
2158 
2159     public boolean isEmpty() {
2160         return false;
2161     }
2162 
2163     public boolean containsKey(Object key) {
2164         return key.equals(OUTPUTSTREAM_PROPERTY);
2165     }
2166 
2167     /**
2168      * Returns the value associated to an implementation-specific
2169      * property.
2170      */
2171     public Object get(Object key) {
2172         if (key.equals(OUTPUTSTREAM_PROPERTY)) {
2173             return fOutputStream;
2174         }
2175         return null;
2176     }
2177 
2178     public java.util.Set entrySet() {
2179         throw new UnsupportedOperationException();
2180     }
2181 
2182     /**
2183      * Overrides the method defined in AbstractMap which is
2184      * not completely implemented. Calling toString() in
2185      * AbstractMap would cause an unsupported exection to
2186      * be thrown.
2187      */
2188     public String toString() {
2189         return getClass().getName() + "@" + Integer.toHexString(hashCode());
2190     }
2191 
2192     /**
2193      * Overrides the method defined in AbstractMap
2194      * This is required by the toString() method
2195      */
2196     public int hashCode() {
2197         return fElementStack.hashCode();
2198     }
2199     /**
2200      * Overrides the method defined in AbstractMap
2201      * This is required to satisfy the contract for hashCode.
2202      */
2203     public boolean equals(Object obj) {
2204         return (this == obj);
2205     }
2206 }