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 XMLStreamWriterBase {
  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      * Writes the XML declaration.
1116      *
1117      * @throws XMLStreamException in case of an IOException
1118      */
1119     public void writeStartDocument() throws XMLStreamException {
1120         writeStartDocument(null, null, false, false);
1121     }
1122 
1123     /**
1124      * Writes the XML declaration.
1125      *
1126      * @param version the specified version
1127      * @throws XMLStreamException in case of an IOException
1128      */
1129     public void writeStartDocument(String version) throws XMLStreamException {
1130         writeStartDocument(null, version, false, false);
1131     }
1132 
1133     /**
1134      * Writes the XML declaration.
1135      *
1136      * @param encoding the specified encoding
1137      * @param version the specified version
1138      * @throws XMLStreamException in case of an IOException
1139      */
1140     @Override
1141     public void writeStartDocument(String encoding, String version)
1142         throws XMLStreamException {
1143         writeStartDocument(encoding, version, false, false);
1144     }
1145 
1146     @Override
1147     public void writeStartDocument(String encoding, String version,
1148             boolean standalone, boolean standaloneSet)
1149         throws XMLStreamException {
1150 
1151         try {
1152             if ((encoding == null || encoding.length() == 0)
1153                     && (version == null || version.length() == 0)
1154                     && (!standaloneSet)) {
1155                 fWriter.write(DEFAULT_XMLDECL);
1156                 return;
1157             }
1158 
1159             // Verify the encoding before writing anything
1160             if (encoding != null && !encoding.equals("")) {
1161                 verifyEncoding(encoding);
1162             }
1163 
1164             fWriter.write("<?xml version=\"");
1165 
1166             if ((version == null) || version.equals("")) {
1167                 fWriter.write(DEFAULT_XML_VERSION);
1168             } else {
1169                 fWriter.write(version);
1170             }
1171 
1172             if (encoding != null && !encoding.equals("")) {
1173                 fWriter.write("\" encoding=\"");
1174                 fWriter.write(encoding);
1175             }
1176 
1177             if (standaloneSet) {
1178                 fWriter.write("\" standalone=\"");
1179                 if (standalone) {
1180                     fWriter.write("yes");
1181                 } else {
1182                     fWriter.write("no");
1183                 }
1184             }
1185 
1186             fWriter.write("\"?>");
1187         } catch (IOException ex) {
1188             throw new XMLStreamException(ex);
1189         }
1190     }
1191 
1192     /**
1193      * Verifies that the encoding is consistent between the underlying encoding
1194      * and that specified.
1195      *
1196      * @param encoding the specified encoding
1197      * @throws XMLStreamException if they do not match
1198      */
1199     private void verifyEncoding(String encoding) throws XMLStreamException {
1200 
1201         String streamEncoding = null;
1202         if (fWriter instanceof OutputStreamWriter) {
1203             streamEncoding = ((OutputStreamWriter) fWriter).getEncoding();
1204         }
1205         else if (fWriter instanceof UTF8OutputStreamWriter) {
1206             streamEncoding = ((UTF8OutputStreamWriter) fWriter).getEncoding();
1207         }
1208         else if (fWriter instanceof XMLWriter) {
1209             streamEncoding = ((OutputStreamWriter) ((XMLWriter)fWriter).getWriter()).getEncoding();
1210         }
1211 
1212         if (streamEncoding != null && !streamEncoding.equalsIgnoreCase(encoding)) {
1213             // If the equality check failed, check for charset encoding aliases
1214             boolean foundAlias = false;
1215             Set aliases = Charset.forName(encoding).aliases();
1216             for (Iterator it = aliases.iterator(); !foundAlias && it.hasNext(); ) {
1217                 if (streamEncoding.equalsIgnoreCase((String) it.next())) {
1218                     foundAlias = true;
1219                 }
1220             }
1221             // If no alias matches the encoding name, then report error
1222             if (!foundAlias) {
1223                 throw new XMLStreamException("Underlying stream encoding '"
1224                         + streamEncoding
1225                         + "' and input paramter for writeStartDocument() method '"
1226                         + encoding + "' do not match.");
1227             }
1228         }
1229     }
1230 
1231     /**
1232      * @param localName
1233      * @throws XMLStreamException
1234      */
1235     public void writeStartElement(String localName) throws XMLStreamException {
1236         try {
1237             if (localName == null) {
1238                 throw new XMLStreamException("Local Name cannot be null");
1239             }
1240 
1241             if (fStartTagOpened) {
1242                 closeStartTag();
1243             }
1244 
1245             openStartTag();
1246             fElementStack.push(null, localName, null, null, false);
1247             fInternalNamespaceContext.pushContext();
1248 
1249             if (fIsRepairingNamespace) {
1250                 return;
1251             }
1252 
1253             fWriter.write(localName);
1254         } catch (IOException ex) {
1255             throw new XMLStreamException(ex);
1256         }
1257     }
1258 
1259     /**
1260      * @param namespaceURI
1261      * @param localName
1262      * @throws XMLStreamException
1263      */
1264     public void writeStartElement(String namespaceURI, String localName)
1265         throws XMLStreamException {
1266         if (localName == null) {
1267             throw new XMLStreamException("Local Name cannot be null");
1268         }
1269 
1270         if (namespaceURI == null) {
1271             throw new XMLStreamException("NamespaceURI cannot be null");
1272         }
1273 
1274         namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1275 
1276         String prefix = null;
1277 
1278         if (!fIsRepairingNamespace) {
1279             prefix = fNamespaceContext.getPrefix(namespaceURI);
1280 
1281             if (prefix != null) {
1282                 prefix = fSymbolTable.addSymbol(prefix);
1283             }
1284         }
1285 
1286         writeStartElement(prefix, localName, namespaceURI);
1287     }
1288 
1289     /**
1290      * @param prefix
1291      * @param localName
1292      * @param namespaceURI
1293      * @throws XMLStreamException
1294      */
1295     public void writeStartElement(String prefix, String localName,
1296         String namespaceURI) throws XMLStreamException {
1297         try {
1298             if (localName == null) {
1299                 throw new XMLStreamException("Local Name cannot be null");
1300             }
1301 
1302             if (namespaceURI == null) {
1303                 throw new XMLStreamException("NamespaceURI cannot be null");
1304             }
1305 
1306             if (!fIsRepairingNamespace) {
1307                 if (prefix == null) {
1308                     throw new XMLStreamException("Prefix cannot be null");
1309                 }
1310             }
1311 
1312             if (fStartTagOpened) {
1313                 closeStartTag();
1314             }
1315 
1316             openStartTag();
1317             namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1318 
1319             if (prefix != null) {
1320                 prefix = fSymbolTable.addSymbol(prefix);
1321             }
1322 
1323             fElementStack.push(prefix, localName, null, namespaceURI, false);
1324             fInternalNamespaceContext.pushContext();
1325 
1326             String tmpPrefix = fNamespaceContext.getPrefix(namespaceURI);
1327 
1328 
1329             if ((prefix != null) &&
1330                     ((tmpPrefix == null) || !prefix.equals(tmpPrefix))) {
1331                 fInternalNamespaceContext.declarePrefix(prefix, namespaceURI);
1332 
1333             }
1334 
1335             if (fIsRepairingNamespace) {
1336                 if ((prefix == null) ||
1337                         ((tmpPrefix != null) && prefix.equals(tmpPrefix))) {
1338                     return;
1339                 }
1340 
1341                 QName qname = new QName();
1342                 qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null,
1343                     namespaceURI);
1344                 fNamespaceDecls.add(qname);
1345 
1346                 return;
1347             }
1348 
1349             if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1350                 fWriter.write(prefix);
1351                 fWriter.write(":");
1352             }
1353 
1354             fWriter.write(localName);
1355 
1356         } catch (IOException ex) {
1357             throw new XMLStreamException(ex);
1358         }
1359     }
1360 
1361     /**
1362      * Writes character reference in hex format.
1363      */
1364     private void writeCharRef(int codePoint) throws IOException {
1365         fWriter.write( "&#x" );
1366         fWriter.write( Integer.toHexString(codePoint) );
1367         fWriter.write( ';' );
1368     }
1369 
1370     /**
1371      * Writes XML content to underlying writer. Escapes characters unless
1372      * escaping character feature is turned off.
1373      */
1374     private void writeXMLContent(char[] content, int start, int length,
1375         boolean escapeChars) throws IOException {
1376         if (!escapeChars) {
1377             fWriter.write(content, start, length);
1378 
1379             return;
1380         }
1381 
1382         // Index of the next char to be written
1383         int startWritePos = start;
1384 
1385         final int end = start + length;
1386 
1387         for (int index = start; index < end; index++) {
1388             char ch = content[index];
1389 
1390             if (fEncoder != null && !fEncoder.canEncode(ch)){
1391                 fWriter.write(content, startWritePos, index - startWritePos );
1392 
1393                 // Check if current and next characters forms a surrogate pair
1394                 // and escape it to avoid generation of invalid xml content
1395                 if ( index != end - 1 && Character.isSurrogatePair(ch, content[index+1])) {
1396                     writeCharRef(Character.toCodePoint(ch, content[index+1]));
1397                     index++;
1398                 } else {
1399                     writeCharRef(ch);
1400                 }
1401                 startWritePos = index + 1;
1402                 continue;
1403             }
1404 
1405             switch (ch) {
1406             case '<':
1407                 fWriter.write(content, startWritePos, index - startWritePos);
1408                 fWriter.write("&lt;");
1409                 startWritePos = index + 1;
1410 
1411                 break;
1412 
1413             case '&':
1414                 fWriter.write(content, startWritePos, index - startWritePos);
1415                 fWriter.write("&amp;");
1416                 startWritePos = index + 1;
1417 
1418                 break;
1419 
1420             case '>':
1421                 fWriter.write(content, startWritePos, index - startWritePos);
1422                 fWriter.write("&gt;");
1423                 startWritePos = index + 1;
1424 
1425                 break;
1426             }
1427         }
1428 
1429         // Write any pending data
1430         fWriter.write(content, startWritePos, end - startWritePos);
1431     }
1432 
1433     private void writeXMLContent(String content) throws IOException {
1434         if ((content != null) && (content.length() > 0)) {
1435             writeXMLContent(content,
1436                     fEscapeCharacters,  // boolean = escapeChars
1437                     false);             // false = escapeDoubleQuotes
1438         }
1439     }
1440 
1441     /**
1442      * Writes XML content to underlying writer. Escapes characters unless
1443      * escaping character feature is turned off.
1444      */
1445     private void writeXMLContent(
1446             String content,
1447             boolean escapeChars,
1448             boolean escapeDoubleQuotes)
1449         throws IOException {
1450 
1451         if (!escapeChars) {
1452             fWriter.write(content);
1453 
1454             return;
1455         }
1456 
1457         // Index of the next char to be written
1458         int startWritePos = 0;
1459 
1460         final int end = content.length();
1461 
1462         for (int index = 0; index < end; index++) {
1463             char ch = content.charAt(index);
1464 
1465             if (fEncoder != null && !fEncoder.canEncode(ch)){
1466                 fWriter.write(content, startWritePos, index - startWritePos );
1467 
1468                 // Check if current and next characters forms a surrogate pair
1469                 // and escape it to avoid generation of invalid xml content
1470                 if ( index != end - 1 && Character.isSurrogatePair(ch, content.charAt(index+1))) {
1471                     writeCharRef(Character.toCodePoint(ch, content.charAt(index+1)));
1472                     index++;
1473                 } else {
1474                     writeCharRef(ch);
1475                 }
1476 
1477                 startWritePos = index + 1;
1478                 continue;
1479             }
1480 
1481             switch (ch) {
1482             case '<':
1483                 fWriter.write(content, startWritePos, index - startWritePos);
1484                 fWriter.write("&lt;");
1485                 startWritePos = index + 1;
1486 
1487                 break;
1488 
1489             case '&':
1490                 fWriter.write(content, startWritePos, index - startWritePos);
1491                 fWriter.write("&amp;");
1492                 startWritePos = index + 1;
1493 
1494                 break;
1495 
1496             case '>':
1497                 fWriter.write(content, startWritePos, index - startWritePos);
1498                 fWriter.write("&gt;");
1499                 startWritePos = index + 1;
1500 
1501                 break;
1502 
1503             case '"':
1504                 fWriter.write(content, startWritePos, index - startWritePos);
1505                 if (escapeDoubleQuotes) {
1506                     fWriter.write("&quot;");
1507                 } else {
1508                     fWriter.write('"');
1509                 }
1510                 startWritePos = index + 1;
1511 
1512                 break;
1513             }
1514         }
1515 
1516         // Write any pending data
1517         fWriter.write(content, startWritePos, end - startWritePos);
1518     }
1519 
1520     /**
1521      * marks close of start tag and writes the same into the writer.
1522      */
1523     private void closeStartTag() throws XMLStreamException {
1524         try {
1525             ElementState currentElement = fElementStack.peek();
1526 
1527             if (fIsRepairingNamespace) {
1528                 repair();
1529                 correctPrefix(currentElement, XMLStreamConstants.START_ELEMENT);
1530 
1531                 if ((currentElement.prefix != null) &&
1532                         (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1533                     fWriter.write(currentElement.prefix);
1534                     fWriter.write(":");
1535                 }
1536 
1537                 fWriter.write(currentElement.localpart);
1538 
1539                 int len = fNamespaceDecls.size();
1540                 QName qname = null;
1541 
1542                 for (int i = 0; i < len; i++) {
1543                     qname = (QName) fNamespaceDecls.get(i);
1544 
1545                     if (qname != null) {
1546                         if (fInternalNamespaceContext.declarePrefix(qname.prefix,
1547                             qname.uri)) {
1548                             writenamespace(qname.prefix, qname.uri);
1549                         }
1550                     }
1551                 }
1552 
1553                 fNamespaceDecls.clear();
1554 
1555                 Attribute attr = null;
1556 
1557                 for (int j = 0; j < fAttributeCache.size(); j++) {
1558                     attr = (Attribute) fAttributeCache.get(j);
1559 
1560                     if ((attr.prefix != null) && (attr.uri != null)) {
1561                         if (!attr.prefix.equals("") && !attr.uri.equals("") ) {
1562                             String tmp = fInternalNamespaceContext.getPrefix(attr.uri);
1563 
1564                             if ((tmp == null) || (tmp != attr.prefix)) {
1565                                 tmp = getAttrPrefix(attr.uri);
1566                                 if (tmp == null) {
1567                                     if (fInternalNamespaceContext.declarePrefix(attr.prefix,
1568                                         attr.uri)) {
1569                                         writenamespace(attr.prefix, attr.uri);
1570                                     }
1571                                 } else {
1572                                     writenamespace(attr.prefix, attr.uri);
1573                                 }
1574                             }
1575                         }
1576                     }
1577 
1578                     writeAttributeWithPrefix(attr.prefix, attr.localpart,
1579                         attr.value);
1580                 }
1581                 fAttrNamespace = null;
1582                 fAttributeCache.clear();
1583             }
1584 
1585             if (currentElement.isEmpty) {
1586                 fElementStack.pop();
1587                 fInternalNamespaceContext.popContext();
1588                 fWriter.write(CLOSE_EMPTY_ELEMENT);
1589             } else {
1590                 fWriter.write(CLOSE_START_TAG);
1591             }
1592 
1593             fStartTagOpened = false;
1594         } catch (IOException ex) {
1595             fStartTagOpened = false;
1596             throw new XMLStreamException(ex);
1597         }
1598     }
1599 
1600     /**
1601      * marks open of start tag and writes the same into the writer.
1602      */
1603     private void openStartTag() throws IOException {
1604         fStartTagOpened = true;
1605         fWriter.write(OPEN_START_TAG);
1606     }
1607 
1608     /**
1609      *
1610      * @param uri
1611      * @return
1612      */
1613     private void correctPrefix(QName attr, int type) {
1614         String tmpPrefix = null;
1615         String prefix;
1616         String uri;
1617         prefix = attr.prefix;
1618         uri = attr.uri;
1619         boolean isSpecialCaseURI = false;
1620 
1621         if (prefix == null || prefix.equals("")) {
1622             if (uri == null) {
1623                 return;
1624             }
1625 
1626             if (prefix == XMLConstants.DEFAULT_NS_PREFIX && uri == XMLConstants.DEFAULT_NS_PREFIX)
1627                 return;
1628 
1629             uri = fSymbolTable.addSymbol(uri);
1630 
1631             QName decl = null;
1632 
1633             for (int i = 0; i < fNamespaceDecls.size(); i++) {
1634                 decl = (QName) fNamespaceDecls.get(i);
1635 
1636                 if ((decl != null) && (decl.uri == attr.uri)) {
1637                     attr.prefix = decl.prefix;
1638 
1639                     return;
1640                 }
1641             }
1642 
1643             tmpPrefix = fNamespaceContext.getPrefix(uri);
1644 
1645             if (tmpPrefix == XMLConstants.DEFAULT_NS_PREFIX) {
1646                 if (type == XMLStreamConstants.START_ELEMENT) {
1647                     return;
1648                 }
1649                 else if (type == XMLStreamConstants.ATTRIBUTE) {
1650                     //the uri happens to be the same as that of the default namespace
1651                     tmpPrefix = getAttrPrefix(uri);
1652                     isSpecialCaseURI = true;
1653                 }
1654             }
1655 
1656             if (tmpPrefix == null) {
1657                 StringBuffer genPrefix = new StringBuffer("zdef");
1658 
1659                 for (int i = 0; i < 1; i++) {
1660                     genPrefix.append(fPrefixGen.nextInt());
1661                 }
1662 
1663                 prefix = genPrefix.toString();
1664                 prefix = fSymbolTable.addSymbol(prefix);
1665             } else {
1666                 prefix = fSymbolTable.addSymbol(tmpPrefix);
1667             }
1668 
1669             if (tmpPrefix == null) {
1670                 if (isSpecialCaseURI) {
1671                     addAttrNamespace(prefix, uri);
1672                 } else {
1673                     QName qname = new QName();
1674                     qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, uri);
1675                     fNamespaceDecls.add(qname);
1676                     fInternalNamespaceContext.declarePrefix(fSymbolTable.addSymbol(
1677                         prefix), uri);
1678                 }
1679             }
1680         }
1681 
1682         attr.prefix = prefix;
1683     }
1684 
1685     /**
1686      * return the prefix if the attribute has an uri the same as that of the default namespace
1687      */
1688     private String getAttrPrefix(String uri) {
1689         if (fAttrNamespace != null) {
1690             return (String)fAttrNamespace.get(uri);
1691         }
1692         return null;
1693     }
1694     private void addAttrNamespace(String prefix, String uri) {
1695         if (fAttrNamespace == null) {
1696             fAttrNamespace = new HashMap();
1697         }
1698         fAttrNamespace.put(prefix, uri);
1699     }
1700     /**
1701      * @param uri
1702      * @return
1703      */
1704     private boolean isDefaultNamespace(String uri) {
1705         String defaultNamespace = fInternalNamespaceContext.getURI(DEFAULT_PREFIX);
1706 
1707         if (uri == defaultNamespace) {
1708             return true;
1709         }
1710 
1711         return false;
1712     }
1713 
1714     /**
1715      * @param prefix
1716      * @param uri
1717      * @return
1718      */
1719     private boolean checkUserNamespaceContext(String prefix, String uri) {
1720         if (fNamespaceContext.userContext != null) {
1721             String tmpURI = fNamespaceContext.userContext.getNamespaceURI(prefix);
1722 
1723             if ((tmpURI != null) && tmpURI.equals(uri)) {
1724                 return true;
1725             }
1726         }
1727 
1728         return false;
1729     }
1730 
1731     /**
1732      * Correct's namespaces  as per requirements of isReparisingNamespace property.
1733      */
1734     protected void repair() {
1735         Attribute attr = null;
1736         Attribute attr2 = null;
1737         ElementState currentElement = fElementStack.peek();
1738         removeDuplicateDecls();
1739 
1740         for(int i=0 ; i< fAttributeCache.size();i++){
1741             attr = (Attribute)fAttributeCache.get(i);
1742             if((attr.prefix != null && !attr.prefix.equals("")) || (attr.uri != null && !attr.uri.equals(""))) {
1743                 correctPrefix(currentElement,attr);
1744             }
1745         }
1746 
1747         if (!isDeclared(currentElement)) {
1748             if ((currentElement.prefix != null) &&
1749                     (currentElement.uri != null)) {
1750                 if ((!currentElement.prefix.equals("")) && (!currentElement.uri.equals(""))) {
1751                     fNamespaceDecls.add(currentElement);
1752                 }
1753             }
1754         }
1755 
1756         for(int i=0 ; i< fAttributeCache.size();i++){
1757             attr = (Attribute)fAttributeCache.get(i);
1758             for(int j=i+1;j<fAttributeCache.size();j++){
1759                 attr2 = (Attribute)fAttributeCache.get(j);
1760                 if(!"".equals(attr.prefix)&& !"".equals(attr2.prefix)){
1761                     correctPrefix(attr,attr2);
1762                 }
1763             }
1764         }
1765 
1766         repairNamespaceDecl(currentElement);
1767 
1768         int i = 0;
1769 
1770         for (i = 0; i < fAttributeCache.size(); i++) {
1771             attr = (Attribute) fAttributeCache.get(i);
1772             /* If 'attr' is an attribute and it is in no namespace(which means that prefix="", uri=""), attr's
1773                namespace should not be redinded. See [http://www.w3.org/TR/REC-xml-names/#defaulting].
1774              */
1775             if (attr.prefix != null && attr.prefix.equals("") && attr.uri != null && attr.uri.equals("")){
1776                 repairNamespaceDecl(attr);
1777             }
1778         }
1779 
1780         QName qname = null;
1781 
1782         for (i = 0; i < fNamespaceDecls.size(); i++) {
1783             qname = (QName) fNamespaceDecls.get(i);
1784 
1785             if (qname != null) {
1786                 fInternalNamespaceContext.declarePrefix(qname.prefix, qname.uri);
1787             }
1788         }
1789 
1790         for (i = 0; i < fAttributeCache.size(); i++) {
1791             attr = (Attribute) fAttributeCache.get(i);
1792             correctPrefix(attr, XMLStreamConstants.ATTRIBUTE);
1793         }
1794     }
1795 
1796     /*
1797      *If element and/or attribute names in the same start or empty-element tag
1798      *are bound to different namespace URIs and are using the same prefix then
1799      *the element or the first occurring attribute retains the original prefix
1800      *and the following attributes have their prefixes replaced with a new prefix
1801      *that is bound to the namespace URIs of those attributes.
1802      */
1803     void correctPrefix(QName attr1, QName attr2) {
1804         String tmpPrefix = null;
1805         QName decl = null;
1806         boolean done = false;
1807 
1808         checkForNull(attr1);
1809         checkForNull(attr2);
1810 
1811         if(attr1.prefix.equals(attr2.prefix) && !(attr1.uri.equals(attr2.uri))){
1812 
1813             tmpPrefix = fNamespaceContext.getPrefix(attr2.uri);
1814 
1815             if (tmpPrefix != null) {
1816                 attr2.prefix = fSymbolTable.addSymbol(tmpPrefix);
1817             } else {
1818                 decl = null;
1819                 for(int n=0;n<fNamespaceDecls.size();n++){
1820                     decl = (QName)fNamespaceDecls.get(n);
1821                     if(decl != null && (decl.uri == attr2.uri)){
1822                         attr2.prefix = decl.prefix;
1823 
1824                         return;
1825                     }
1826                 }
1827 
1828                 //No namespace mapping found , so declare prefix.
1829                 StringBuffer genPrefix = new StringBuffer("zdef");
1830 
1831                 for (int k = 0; k < 1; k++) {
1832                     genPrefix.append(fPrefixGen.nextInt());
1833                 }
1834 
1835                 tmpPrefix = genPrefix.toString();
1836                 tmpPrefix = fSymbolTable.addSymbol(tmpPrefix);
1837                 attr2.prefix = tmpPrefix;
1838 
1839                 QName qname = new QName();
1840                 qname.setValues(tmpPrefix, XMLConstants.XMLNS_ATTRIBUTE, null,
1841                     attr2.uri);
1842                 fNamespaceDecls.add(qname);
1843             }
1844         }
1845     }
1846 
1847     void checkForNull(QName attr) {
1848         if (attr.prefix == null) attr.prefix = XMLConstants.DEFAULT_NS_PREFIX;
1849         if (attr.uri == null) attr.uri = XMLConstants.DEFAULT_NS_PREFIX;
1850     }
1851 
1852     void removeDuplicateDecls(){
1853         QName decl1,decl2;
1854         for(int i =0;i<fNamespaceDecls.size();i++){
1855             decl1 = (QName)fNamespaceDecls.get(i);
1856             if(decl1!=null) {
1857                 for(int j=i+1;j<fNamespaceDecls.size();j++){
1858                     decl2 = (QName)fNamespaceDecls.get(j);
1859                     // QName.equals relies on identity equality, so we can't use it,
1860                     // because prefixes aren't interned
1861                     if(decl2!=null && decl1.prefix.equals(decl2.prefix) && decl1.uri.equals(decl2.uri))
1862                         fNamespaceDecls.remove(j);
1863                 }
1864             }
1865         }
1866     }
1867 
1868     /*
1869      *If an element or attribute name is bound to a prefix and there is a namespace
1870      *declaration that binds that prefix to a different URI then that namespace declaration
1871      *is either removed if the correct mapping is inherited from the parent context of that element,
1872      *or changed to the namespace URI of the element or attribute using that prefix.
1873      *
1874      */
1875     void repairNamespaceDecl(QName attr) {
1876         QName decl = null;
1877         String tmpURI;
1878 
1879         //check for null prefix.
1880         for (int j = 0; j < fNamespaceDecls.size(); j++) {
1881             decl = (QName) fNamespaceDecls.get(j);
1882 
1883             if (decl != null) {
1884                 if ((attr.prefix != null) &&
1885                         (attr.prefix.equals(decl.prefix) &&
1886                         !(attr.uri.equals(decl.uri)))) {
1887                     tmpURI = fNamespaceContext.getNamespaceURI(attr.prefix);
1888 
1889                     //see if you need to add to symbole table.
1890                     if (tmpURI != null) {
1891                         if (tmpURI.equals(attr.uri)) {
1892                             fNamespaceDecls.set(j, null);
1893                         } else {
1894                             decl.uri = attr.uri;
1895                         }
1896                     }
1897                 }
1898             }
1899         }
1900     }
1901 
1902     boolean isDeclared(QName attr) {
1903         QName decl = null;
1904 
1905         for (int n = 0; n < fNamespaceDecls.size(); n++) {
1906             decl = (QName) fNamespaceDecls.get(n);
1907 
1908             if ((attr.prefix != null) &&
1909                     ((attr.prefix == decl.prefix) && (decl.uri == attr.uri))) {
1910                 return true;
1911             }
1912         }
1913 
1914         if (attr.uri != null) {
1915             if (fNamespaceContext.getPrefix(attr.uri) != null) {
1916                 return true;
1917             }
1918         }
1919 
1920         return false;
1921     }
1922 
1923     /*
1924      * Start of Internal classes.
1925      *
1926      */
1927     protected class ElementStack {
1928         /** The stack data. */
1929         protected ElementState[] fElements;
1930 
1931         /** The size of the stack. */
1932         protected short fDepth;
1933 
1934         /** Default constructor. */
1935         public ElementStack() {
1936             fElements = new ElementState[10];
1937 
1938             for (int i = 0; i < fElements.length; i++) {
1939                 fElements[i] = new ElementState();
1940             }
1941         }
1942 
1943         /**
1944          * Pushes an element on the stack.
1945          * <p>
1946          * <strong>Note:</strong> The QName values are copied into the
1947          * stack. In other words, the caller does <em>not</em> orphan
1948          * the element to the stack. Also, the QName object returned
1949          * is <em>not</em> orphaned to the caller. It should be
1950          * considered read-only.
1951          *
1952          * @param element The element to push onto the stack.
1953          *
1954          * @return Returns the actual QName object that stores the
1955          */
1956         public ElementState push(ElementState element) {
1957             if (fDepth == fElements.length) {
1958                 ElementState[] array = new ElementState[fElements.length * 2];
1959                 System.arraycopy(fElements, 0, array, 0, fDepth);
1960                 fElements = array;
1961 
1962                 for (int i = fDepth; i < fElements.length; i++) {
1963                     fElements[i] = new ElementState();
1964                 }
1965             }
1966 
1967             fElements[fDepth].setValues(element);
1968 
1969             return fElements[fDepth++];
1970         }
1971 
1972         /**
1973          *
1974          * @param prefix
1975          * @param localpart
1976          * @param rawname
1977          * @param uri
1978          * @param isEmpty
1979          * @return
1980          */
1981         public ElementState push(String prefix, String localpart,
1982             String rawname, String uri, boolean isEmpty) {
1983             if (fDepth == fElements.length) {
1984                 ElementState[] array = new ElementState[fElements.length * 2];
1985                 System.arraycopy(fElements, 0, array, 0, fDepth);
1986                 fElements = array;
1987 
1988                 for (int i = fDepth; i < fElements.length; i++) {
1989                     fElements[i] = new ElementState();
1990                 }
1991             }
1992 
1993             fElements[fDepth].setValues(prefix, localpart, rawname, uri, isEmpty);
1994 
1995             return fElements[fDepth++];
1996         }
1997 
1998         /**
1999          * Pops an element off of the stack by setting the values of
2000          * the specified QName.
2001          * <p>
2002          * <strong>Note:</strong> The object returned is <em>not</em>
2003          * orphaned to the caller. Therefore, the caller should consider
2004          * the object to be read-only.
2005          */
2006         public ElementState pop() {
2007             return fElements[--fDepth];
2008         }
2009 
2010         /** Clears the stack without throwing away existing QName objects. */
2011         public void clear() {
2012             fDepth = 0;
2013         }
2014 
2015         /**
2016          * This function is as a result of optimization done for endElement --
2017          * we dont need to set the value for every end element we encouter.
2018          * For Well formedness checks we can have the same QName object that was pushed.
2019          * the values will be set only if application need to know about the endElement
2020          * -- neeraj.bajaj@sun.com
2021          */
2022         public ElementState peek() {
2023             return fElements[fDepth - 1];
2024         }
2025 
2026         /**
2027          *
2028          * @return
2029          */
2030         public boolean empty() {
2031             return (fDepth > 0) ? false : true;
2032         }
2033     }
2034 
2035     /**
2036      * Maintains element state . localName for now.
2037      */
2038     class ElementState extends QName {
2039         public boolean isEmpty = false;
2040 
2041         public ElementState() {}
2042 
2043         public ElementState(String prefix, String localpart, String rawname,
2044             String uri) {
2045             super(prefix, localpart, rawname, uri);
2046         }
2047 
2048         public void setValues(String prefix, String localpart, String rawname,
2049             String uri, boolean isEmpty) {
2050             super.setValues(prefix, localpart, rawname, uri);
2051             this.isEmpty = isEmpty;
2052         }
2053     }
2054 
2055     /**
2056      * Attributes
2057      */
2058     class Attribute extends QName {
2059         String value;
2060 
2061         Attribute(String value) {
2062             super();
2063             this.value = value;
2064         }
2065     }
2066 
2067     /**
2068      * Implementation of NamespaceContext .
2069      *
2070      */
2071     class NamespaceContextImpl implements NamespaceContext {
2072         //root namespace context set by user.
2073         NamespaceContext userContext = null;
2074 
2075         //context built by the writer.
2076         NamespaceSupport internalContext = null;
2077 
2078         public String getNamespaceURI(String prefix) {
2079             String uri = null;
2080 
2081             if (prefix != null) {
2082                 prefix = fSymbolTable.addSymbol(prefix);
2083             }
2084 
2085             if (internalContext != null) {
2086                 uri = internalContext.getURI(prefix);
2087 
2088                 if (uri != null) {
2089                     return uri;
2090                 }
2091             }
2092 
2093             if (userContext != null) {
2094                 uri = userContext.getNamespaceURI(prefix);
2095 
2096                 return uri;
2097             }
2098 
2099             return null;
2100         }
2101 
2102         public String getPrefix(String uri) {
2103             String prefix = null;
2104 
2105             if (uri != null) {
2106                 uri = fSymbolTable.addSymbol(uri);
2107             }
2108 
2109             if (internalContext != null) {
2110                 prefix = internalContext.getPrefix(uri);
2111 
2112                 if (prefix != null) {
2113                     return prefix;
2114                 }
2115             }
2116 
2117             if (userContext != null) {
2118                 return userContext.getPrefix(uri);
2119             }
2120 
2121             return null;
2122         }
2123 
2124         public java.util.Iterator getPrefixes(String uri) {
2125             Vector prefixes = null;
2126             Iterator itr = null;
2127 
2128             if (uri != null) {
2129                 uri = fSymbolTable.addSymbol(uri);
2130             }
2131 
2132             if (userContext != null) {
2133                 itr = userContext.getPrefixes(uri);
2134             }
2135 
2136             if (internalContext != null) {
2137                 prefixes = internalContext.getPrefixes(uri);
2138             }
2139 
2140             if ((prefixes == null) && (itr != null)) {
2141                 return itr;
2142             } else if ((prefixes != null) && (itr == null)) {
2143                 return new ReadOnlyIterator(prefixes.iterator());
2144             } else if ((prefixes != null) && (itr != null)) {
2145                 String ob = null;
2146 
2147                 while (itr.hasNext()) {
2148                     ob = (String) itr.next();
2149 
2150                     if (ob != null) {
2151                         ob = fSymbolTable.addSymbol(ob);
2152                     }
2153 
2154                     if (!prefixes.contains(ob)) {
2155                         prefixes.add(ob);
2156                     }
2157                 }
2158 
2159                 return new ReadOnlyIterator(prefixes.iterator());
2160             }
2161 
2162             return fReadOnlyIterator;
2163         }
2164     }
2165 
2166     // -- Map Interface --------------------------------------------------
2167 
2168     public int size() {
2169         return 1;
2170     }
2171 
2172     public boolean isEmpty() {
2173         return false;
2174     }
2175 
2176     public boolean containsKey(Object key) {
2177         return key.equals(OUTPUTSTREAM_PROPERTY);
2178     }
2179 
2180     /**
2181      * Returns the value associated to an implementation-specific
2182      * property.
2183      */
2184     public Object get(Object key) {
2185         if (key.equals(OUTPUTSTREAM_PROPERTY)) {
2186             return fOutputStream;
2187         }
2188         return null;
2189     }
2190 
2191     public java.util.Set entrySet() {
2192         throw new UnsupportedOperationException();
2193     }
2194 
2195     /**
2196      * Overrides the method defined in AbstractMap which is
2197      * not completely implemented. Calling toString() in
2198      * AbstractMap would cause an unsupported exection to
2199      * be thrown.
2200      */
2201     public String toString() {
2202         return getClass().getName() + "@" + Integer.toHexString(hashCode());
2203     }
2204 
2205     /**
2206      * Overrides the method defined in AbstractMap
2207      * This is required by the toString() method
2208      */
2209     public int hashCode() {
2210         return fElementStack.hashCode();
2211     }
2212     /**
2213      * Overrides the method defined in AbstractMap
2214      * This is required to satisfy the contract for hashCode.
2215      */
2216     public boolean equals(Object obj) {
2217         return (this == obj);
2218     }
2219 }