1/*
2 * Copyright (c) 2005, 2013, 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.buffer.stax;
27
28import com.sun.xml.internal.stream.buffer.AbstractProcessor;
29import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
30
31import java.io.IOException;
32import java.util.Collections;
33import java.util.HashSet;
34import java.util.Map;
35import java.util.Set;
36
37import com.sun.xml.internal.org.jvnet.staxex.Base64Data;
38import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx;
39
40import javax.xml.stream.XMLStreamException;
41import javax.xml.stream.XMLStreamWriter;
42
43
44/**
45 * A processor of a {@link XMLStreamBuffer} that writes the XML infoset to a
46 * {@link XMLStreamWriter}.
47 *
48 * @author Paul.Sandoz@Sun.Com
49 * @author K.Venugopal@sun.com
50 */
51public class StreamWriterBufferProcessor extends AbstractProcessor {
52
53
54    public StreamWriterBufferProcessor() {
55    }
56
57    /**
58     * @deprecated
59     *      Use {@link #StreamWriterBufferProcessor(XMLStreamBuffer, boolean)}
60     */
61    public StreamWriterBufferProcessor(XMLStreamBuffer buffer) {
62        setXMLStreamBuffer(buffer,buffer.isFragment());
63    }
64
65    /**
66     * @param produceFragmentEvent
67     *      True to generate fragment SAX events without start/endDocument.
68     *      False to generate a full document SAX events.
69     */
70    public StreamWriterBufferProcessor(XMLStreamBuffer buffer,boolean produceFragmentEvent) {
71        setXMLStreamBuffer(buffer,produceFragmentEvent);
72    }
73
74    public final void process(XMLStreamBuffer buffer, XMLStreamWriter writer) throws XMLStreamException {
75        setXMLStreamBuffer(buffer,buffer.isFragment());
76        process(writer);
77    }
78
79    public void process(XMLStreamWriter writer) throws XMLStreamException {
80        if(_fragmentMode){
81            writeFragment(writer);
82        }else{
83            write(writer);
84        }
85    }
86
87    /**
88     * @deprecated
89     *      Use {@link #setXMLStreamBuffer(XMLStreamBuffer, boolean)}
90     */
91    public void setXMLStreamBuffer(XMLStreamBuffer buffer) {
92        setBuffer(buffer);
93    }
94
95    /**
96     * @param produceFragmentEvent
97     *      True to generate fragment SAX events without start/endDocument.
98     *      False to generate a full document SAX events.
99     */
100    public void setXMLStreamBuffer(XMLStreamBuffer buffer, boolean produceFragmentEvent) {
101        setBuffer(buffer,produceFragmentEvent);
102    }
103
104    /**
105     * Writes a full XML infoset event to the given writer,
106     * including start/end document.
107     * Any inscope namespaces present will be written as namespace
108     * delcarations on each top-level element.
109     */
110    public void write(XMLStreamWriter writer) throws XMLStreamException{
111
112        if(!_fragmentMode) {
113            if(_treeCount>1)
114                throw new IllegalStateException("forest cannot be written as a full infoset");
115            writer.writeStartDocument();
116        }
117
118        while(true) {
119            int item = getEIIState(peekStructure());
120            writer.flush();
121
122            switch(item) {
123                case STATE_DOCUMENT:
124                    readStructure(); //skip
125                    break;
126                case STATE_ELEMENT_U_LN_QN:
127                case STATE_ELEMENT_P_U_LN:
128                case STATE_ELEMENT_U_LN:
129                case STATE_ELEMENT_LN:
130                    writeFragment(writer);
131                    break;
132                case STATE_COMMENT_AS_CHAR_ARRAY_SMALL: {
133                    readStructure();
134                    final int length = readStructure();
135                    final int start = readContentCharactersBuffer(length);
136                    final String comment = new String(_contentCharactersBuffer, start, length);
137                    writer.writeComment(comment);
138                    break;
139                }
140                case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM: {
141                    readStructure();
142                    final int length = readStructure16();
143                    final int start = readContentCharactersBuffer(length);
144                    final String comment = new String(_contentCharactersBuffer, start, length);
145                    writer.writeComment(comment);
146                    break;
147                }
148                case STATE_COMMENT_AS_CHAR_ARRAY_COPY: {
149                    readStructure();
150                    final char[] ch = readContentCharactersCopy();
151                    writer.writeComment(new String(ch));
152                    break;
153                }
154                case STATE_PROCESSING_INSTRUCTION:
155                    readStructure();
156                    writer.writeProcessingInstruction(readStructureString(), readStructureString());
157                    break;
158                case STATE_END: // done
159                    readStructure();
160                    writer.writeEndDocument();
161                    return;
162                default:
163                    throw new XMLStreamException("Invalid State "+item);
164            }
165        }
166
167    }
168
169    /**
170     * Writes the buffer as a fragment, meaning
171     * the writer will not receive start/endDocument events.
172     * Any inscope namespaces present will be written as namespace
173     * delcarations on each top-level element.
174     * <p>
175     * If {@link XMLStreamBuffer} has a forest, this method will write all the forests.
176     */
177    public void writeFragment(XMLStreamWriter writer) throws XMLStreamException {
178        if (writer instanceof XMLStreamWriterEx) {
179            writeFragmentEx((XMLStreamWriterEx)writer);
180        } else {
181            writeFragmentNoEx(writer);
182        }
183    }
184
185    public void writeFragmentEx(XMLStreamWriterEx writer) throws XMLStreamException {
186        int depth = 0;  // used to determine when we are done with a tree.
187
188        int item = getEIIState(peekStructure());
189        if(item==STATE_DOCUMENT)
190            readStructure();    // skip STATE_DOCUMENT
191
192        do {
193
194            item = readEiiState();
195
196            switch(item) {
197                case STATE_DOCUMENT:
198                    throw new AssertionError();
199                case STATE_ELEMENT_U_LN_QN: {
200                    depth ++;
201                    final String uri = readStructureString();
202                    final String localName = readStructureString();
203                    final String prefix = getPrefixFromQName(readStructureString());
204                    writer.writeStartElement(prefix,localName,uri);
205                    writeAttributes(writer, isInscope(depth));
206                    break;
207                }
208                case STATE_ELEMENT_P_U_LN: {
209                    depth ++;
210                    final String prefix = readStructureString();
211                    final String uri = readStructureString();
212                    final String localName = readStructureString();
213                    writer.writeStartElement(prefix,localName,uri);
214                    writeAttributes(writer, isInscope(depth));
215                    break;
216                }
217                case STATE_ELEMENT_U_LN: {
218                    depth ++;
219                    final String uri = readStructureString();
220                    final String localName = readStructureString();
221                    writer.writeStartElement("",localName,uri);
222                    writeAttributes(writer, isInscope(depth));
223                    break;
224                }
225                case STATE_ELEMENT_LN: {
226                    depth ++;
227                    final String localName = readStructureString();
228                    writer.writeStartElement(localName);
229                    writeAttributes(writer, isInscope(depth));
230                    break;
231                }
232                case STATE_TEXT_AS_CHAR_ARRAY_SMALL: {
233                    final int length = readStructure();
234                    final int start = readContentCharactersBuffer(length);
235                    writer.writeCharacters(_contentCharactersBuffer,start,length);
236                    break;
237                }
238                case STATE_TEXT_AS_CHAR_ARRAY_MEDIUM: {
239                    final int length = readStructure16();
240                    final int start = readContentCharactersBuffer(length);
241                    writer.writeCharacters(_contentCharactersBuffer,start,length);
242                    break;
243                }
244                case STATE_TEXT_AS_CHAR_ARRAY_COPY: {
245                    char[] c = readContentCharactersCopy();
246                    writer.writeCharacters(c,0,c.length);
247                    break;
248                }
249                case STATE_TEXT_AS_STRING: {
250                    final String s = readContentString();
251                    writer.writeCharacters(s);
252                    break;
253                }
254                case STATE_TEXT_AS_OBJECT: {
255                    final CharSequence c = (CharSequence)readContentObject();
256                    writer.writePCDATA(c);
257                    break;
258                }
259                case STATE_COMMENT_AS_CHAR_ARRAY_SMALL: {
260                    final int length = readStructure();
261                    final int start = readContentCharactersBuffer(length);
262                    final String comment = new String(_contentCharactersBuffer, start, length);
263                    writer.writeComment(comment);
264                    break;
265                }
266                case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM: {
267                    final int length = readStructure16();
268                    final int start = readContentCharactersBuffer(length);
269                    final String comment = new String(_contentCharactersBuffer, start, length);
270                    writer.writeComment(comment);
271                    break;
272                }
273                case STATE_COMMENT_AS_CHAR_ARRAY_COPY: {
274                    final char[] ch = readContentCharactersCopy();
275                    writer.writeComment(new String(ch));
276                    break;
277                }
278                case STATE_PROCESSING_INSTRUCTION:
279                    writer.writeProcessingInstruction(readStructureString(), readStructureString());
280                    break;
281                case STATE_END:
282                    writer.writeEndElement();
283                    depth --;
284                    if(depth==0)
285                        _treeCount--;
286                    break;
287                default:
288                    throw new XMLStreamException("Invalid State "+item);
289            }
290        } while(depth>0 || _treeCount>0);
291
292    }
293
294    public void writeFragmentNoEx(XMLStreamWriter writer) throws XMLStreamException {
295        int depth = 0;
296
297        int item = getEIIState(peekStructure());
298        if(item==STATE_DOCUMENT)
299            readStructure();    // skip STATE_DOCUMENT
300
301        do {
302            item = readEiiState();
303
304            switch(item) {
305                case STATE_DOCUMENT:
306                    throw new AssertionError();
307                case STATE_ELEMENT_U_LN_QN: {
308                    depth ++;
309                    final String uri = readStructureString();
310                    final String localName = readStructureString();
311                    final String prefix = getPrefixFromQName(readStructureString());
312                    writer.writeStartElement(prefix,localName,uri);
313                    writeAttributes(writer, isInscope(depth));
314                    break;
315                }
316                case STATE_ELEMENT_P_U_LN: {
317                    depth ++;
318                    final String prefix = readStructureString();
319                    final String uri = readStructureString();
320                    final String localName = readStructureString();
321                    writer.writeStartElement(prefix,localName,uri);
322                    writeAttributes(writer, isInscope(depth));
323                    break;
324                }
325                case STATE_ELEMENT_U_LN: {
326                    depth ++;
327                    final String uri = readStructureString();
328                    final String localName = readStructureString();
329                    writer.writeStartElement("",localName,uri);
330                    writeAttributes(writer, isInscope(depth));
331                    break;
332                }
333                case STATE_ELEMENT_LN: {
334                    depth ++;
335                    final String localName = readStructureString();
336                    writer.writeStartElement(localName);
337                    writeAttributes(writer, isInscope(depth));
338                    break;
339                }
340                case STATE_TEXT_AS_CHAR_ARRAY_SMALL: {
341                    final int length = readStructure();
342                    final int start = readContentCharactersBuffer(length);
343                    writer.writeCharacters(_contentCharactersBuffer,start,length);
344                    break;
345                }
346                case STATE_TEXT_AS_CHAR_ARRAY_MEDIUM: {
347                    final int length = readStructure16();
348                    final int start = readContentCharactersBuffer(length);
349                    writer.writeCharacters(_contentCharactersBuffer,start,length);
350                    break;
351                }
352                case STATE_TEXT_AS_CHAR_ARRAY_COPY: {
353                    char[] c = readContentCharactersCopy();
354                    writer.writeCharacters(c,0,c.length);
355                    break;
356                }
357                case STATE_TEXT_AS_STRING: {
358                    final String s = readContentString();
359                    writer.writeCharacters(s);
360                    break;
361                }
362                case STATE_TEXT_AS_OBJECT: {
363                    final CharSequence c = (CharSequence)readContentObject();
364                    if (c instanceof Base64Data) {
365                        try {
366                            Base64Data bd = (Base64Data)c;
367                            bd.writeTo(writer);
368                        } catch (IOException e) {
369                          throw new XMLStreamException(e);
370                        }
371                    } else {
372                         writer.writeCharacters(c.toString());
373                    }
374                    break;
375                }
376                case STATE_COMMENT_AS_CHAR_ARRAY_SMALL: {
377                    final int length = readStructure();
378                    final int start = readContentCharactersBuffer(length);
379                    final String comment = new String(_contentCharactersBuffer, start, length);
380                    writer.writeComment(comment);
381                    break;
382                }
383                case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM: {
384                    final int length = readStructure16();
385                    final int start = readContentCharactersBuffer(length);
386                    final String comment = new String(_contentCharactersBuffer, start, length);
387                    writer.writeComment(comment);
388                    break;
389                }
390                case STATE_COMMENT_AS_CHAR_ARRAY_COPY: {
391                    final char[] ch = readContentCharactersCopy();
392                    writer.writeComment(new String(ch));
393                    break;
394                }
395                case STATE_PROCESSING_INSTRUCTION:
396                    writer.writeProcessingInstruction(readStructureString(), readStructureString());
397                    break;
398                case STATE_END:
399                    writer.writeEndElement();
400                    depth --;
401                    if(depth==0)
402                        _treeCount--;
403                    break;
404                default:
405                    throw new XMLStreamException("Invalid State "+item);
406            }
407        } while(depth > 0 || _treeCount>0);
408
409    }
410
411    private boolean isInscope(int depth) {
412        return _buffer.getInscopeNamespaces().size() > 0 && depth ==1;
413    }
414
415    /*
416     * @param inscope: true means write inscope namespaces
417     */
418    private void writeAttributes(XMLStreamWriter writer, boolean inscope) throws XMLStreamException {
419        // prefixSet to collect prefixes that are written before writing inscope namespaces
420        Set<String> prefixSet = inscope ? new HashSet<String>() : Collections.<String>emptySet();
421        int item = peekStructure();
422        if ((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE) {
423            // Skip the namespace declarations on the element
424            // they will have been added already
425            item = writeNamespaceAttributes(item, writer, inscope, prefixSet);
426        }
427        if (inscope) {
428            writeInscopeNamespaces(writer, prefixSet);
429        }
430        if ((item & TYPE_MASK) == T_ATTRIBUTE) {
431            writeAttributes(item, writer);
432        }
433    }
434
435    private static String fixNull(String s) {
436        if (s == null) return "";
437        else return s;
438    }
439
440    /*
441     * @param prefixSet: already written prefixes
442     */
443    private void writeInscopeNamespaces(XMLStreamWriter writer, Set<String> prefixSet) throws XMLStreamException {
444        for (Map.Entry<String, String> e : _buffer.getInscopeNamespaces().entrySet()) {
445            String key = fixNull(e.getKey());
446            // If the prefix is already written, do not write the prefix
447            if (!prefixSet.contains(key)) {
448                writer.writeNamespace(key, e.getValue());
449            }
450        }
451    }
452
453    private int writeNamespaceAttributes(int item, XMLStreamWriter writer, boolean collectPrefixes, Set<String> prefixSet) throws XMLStreamException {
454        do {
455            switch(getNIIState(item)){
456                case STATE_NAMESPACE_ATTRIBUTE:
457                    // Undeclaration of default namespace
458                    writer.writeDefaultNamespace("");
459                    if (collectPrefixes) {
460                        prefixSet.add("");
461                    }
462                    break;
463                case STATE_NAMESPACE_ATTRIBUTE_P:
464                    // Undeclaration of namespace
465                    // Declaration with prefix
466                    String prefix = readStructureString();
467                    writer.writeNamespace(prefix, "");
468                    if (collectPrefixes) {
469                        prefixSet.add(prefix);
470                    }
471                    break;
472                case STATE_NAMESPACE_ATTRIBUTE_P_U:
473                    // Declaration with prefix
474                    prefix = readStructureString();
475                    writer.writeNamespace(prefix, readStructureString());
476                    if (collectPrefixes) {
477                        prefixSet.add(prefix);
478                    }
479                    break;
480                case STATE_NAMESPACE_ATTRIBUTE_U:
481                    // Default declaration
482                    writer.writeDefaultNamespace(readStructureString());
483                    if (collectPrefixes) {
484                        prefixSet.add("");
485                    }
486                    break;
487            }
488            readStructure();
489
490            item = peekStructure();
491        } while((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE);
492
493        return item;
494    }
495
496    private void writeAttributes(int item, XMLStreamWriter writer) throws XMLStreamException {
497        do {
498            switch(getAIIState(item)) {
499                case STATE_ATTRIBUTE_U_LN_QN: {
500                    final String uri = readStructureString();
501                    final String localName = readStructureString();
502                    final String prefix = getPrefixFromQName(readStructureString());
503                    writer.writeAttribute(prefix,uri,localName,readContentString());
504                    break;
505                }
506                case STATE_ATTRIBUTE_P_U_LN:
507                    writer.writeAttribute(readStructureString(), readStructureString(),
508                            readStructureString(), readContentString());
509                    break;
510                case STATE_ATTRIBUTE_U_LN:
511                    writer.writeAttribute(readStructureString(), readStructureString(), readContentString());
512                    break;
513                case STATE_ATTRIBUTE_LN:
514                    writer.writeAttribute(readStructureString(), readContentString());
515                    break;
516            }
517            // Ignore the attribute type
518            readStructureString();
519
520            readStructure();
521
522            item = peekStructure();
523        } while((item & TYPE_MASK) == T_ATTRIBUTE);
524    }
525}
526