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