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