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