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