1/*
2 * Copyright (c) 1997, 2015, 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.bind.v2.runtime.output;
27
28import java.io.IOException;
29import java.io.OutputStream;
30
31import java.io.StringWriter;
32import javax.xml.stream.XMLStreamException;
33
34import com.sun.xml.internal.bind.DatatypeConverterImpl;
35import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
36import com.sun.xml.internal.bind.v2.runtime.Name;
37import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
38import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl;
39
40import org.xml.sax.SAXException;
41
42/**
43 * {@link XmlOutput} implementation specialized for UTF-8.
44 *
45 * @author Kohsuke Kawaguchi
46 * @author Paul Sandoz
47 */
48public class UTF8XmlOutput extends XmlOutputAbstractImpl {
49    protected final OutputStream out;
50
51    /** prefixes encoded. */
52    private Encoded[] prefixes = new Encoded[8];
53
54    /**
55     * Of the {@link #prefixes}, number of filled entries.
56     * This is almost the same as {@link NamespaceContextImpl#count()},
57     * except that it allows us to handle contextual in-scope namespace bindings correctly.
58     */
59    private int prefixCount;
60
61    /** local names encoded in UTF-8. All entries are pre-filled. */
62    private final Encoded[] localNames;
63
64    /** Temporary buffer used to encode text. */
65    /*
66     * TODO
67     * The textBuffer could write directly to the _octetBuffer
68     * when encoding a string if Encoder is modified.
69     * This will avoid an additional memory copy.
70     */
71    private final Encoded textBuffer = new Encoded();
72
73    /** Buffer of octets for writing. */
74    // TODO: Obtain buffer size from property on the JAXB context
75    protected final byte[] octetBuffer = new byte[1024];
76
77    /** Index in buffer to write to. */
78    protected int octetBufferIndex;
79
80    /**
81     * Set to true to indicate that we need to write {@code '>'}
82     * to close a start tag. Deferring the write of this char
83     * allows us to write {@code "/>"} for empty elements.
84     */
85    protected boolean closeStartTagPending = false;
86
87    /**
88     * @see MarshallerImpl#header
89     */
90    private String header;
91
92    private CharacterEscapeHandler escapeHandler = null;
93
94    /**
95     *
96     * @param localNames
97     *      local names encoded in UTF-8.
98     */
99    public UTF8XmlOutput(OutputStream out, Encoded[] localNames, CharacterEscapeHandler escapeHandler) {
100        this.out = out;
101        this.localNames = localNames;
102        for( int i=0; i<prefixes.length; i++ )
103            prefixes[i] = new Encoded();
104        this.escapeHandler = escapeHandler;
105    }
106
107    public void setHeader(String header) {
108        this.header = header;
109    }
110
111    @Override
112    public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException {
113        super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext);
114
115        octetBufferIndex = 0;
116        if(!fragment) {
117            write(XML_DECL);
118        }
119        if(header!=null) {
120            textBuffer.set(header);
121            textBuffer.write(this);
122        }
123    }
124
125    @Override
126    public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
127        flushBuffer();
128        super.endDocument(fragment);
129    }
130
131    /**
132     * Writes {@code '>'} to close the start tag, if necessary.
133     */
134    protected final void closeStartTag() throws IOException {
135        if(closeStartTagPending) {
136            write('>');
137            closeStartTagPending = false;
138        }
139    }
140
141    public void beginStartTag(int prefix, String localName) throws IOException {
142        closeStartTag();
143        int base= pushNsDecls();
144        write('<');
145        writeName(prefix,localName);
146        writeNsDecls(base);
147    }
148
149    @Override
150    public void beginStartTag(Name name) throws IOException {
151        closeStartTag();
152        int base = pushNsDecls();
153        write('<');
154        writeName(name);
155        writeNsDecls(base);
156    }
157
158    private int pushNsDecls() {
159        int total = nsContext.count();
160        NamespaceContextImpl.Element ns = nsContext.getCurrent();
161
162        if(total > prefixes.length) {
163            // reallocate
164            int m = Math.max(total,prefixes.length*2);
165            Encoded[] buf = new Encoded[m];
166            System.arraycopy(prefixes,0,buf,0,prefixes.length);
167            for( int i=prefixes.length; i<buf.length; i++ )
168                buf[i] = new Encoded();
169            prefixes = buf;
170        }
171
172        int base = Math.min(prefixCount,ns.getBase());
173        int size = nsContext.count();
174        for( int i=base; i<size; i++ ) {
175            String p = nsContext.getPrefix(i);
176
177            Encoded e = prefixes[i];
178
179            if(p.length()==0) {
180                e.buf = EMPTY_BYTE_ARRAY;
181                e.len = 0;
182            } else {
183                e.set(p);
184                e.append(':');
185            }
186        }
187        prefixCount = size;
188        return base;
189    }
190
191    protected void writeNsDecls(int base) throws IOException {
192        NamespaceContextImpl.Element ns = nsContext.getCurrent();
193        int size = nsContext.count();
194
195        for( int i=ns.getBase(); i<size; i++ )
196            writeNsDecl(i);
197    }
198
199    /**
200     * Writes a single namespace declaration for the specified prefix.
201     */
202    protected final void writeNsDecl(int prefixIndex) throws IOException {
203        String p = nsContext.getPrefix(prefixIndex);
204
205        if(p.length()==0) {
206            if(nsContext.getCurrent().isRootElement()
207            && nsContext.getNamespaceURI(prefixIndex).length()==0)
208                return;     // no point in declaring xmlns="" on the root element
209            write(XMLNS_EQUALS);
210        } else {
211            Encoded e = prefixes[prefixIndex];
212            write(XMLNS_COLON);
213            write(e.buf,0,e.len-1); // skip the trailing ':'
214            write(EQUALS);
215        }
216        doText(nsContext.getNamespaceURI(prefixIndex),true);
217        write('\"');
218    }
219
220    private void writePrefix(int prefix) throws IOException {
221        prefixes[prefix].write(this);
222    }
223
224    private void writeName(Name name) throws IOException {
225        writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
226        localNames[name.localNameIndex].write(this);
227    }
228
229    private void writeName(int prefix, String localName) throws IOException {
230        writePrefix(prefix);
231        textBuffer.set(localName);
232        textBuffer.write(this);
233    }
234
235    @Override
236    public void attribute(Name name, String value) throws IOException {
237        write(' ');
238        if(name.nsUriIndex==-1) {
239            localNames[name.localNameIndex].write(this);
240        } else
241            writeName(name);
242        write(EQUALS);
243        doText(value,true);
244        write('\"');
245    }
246
247    public void attribute(int prefix, String localName, String value) throws IOException {
248        write(' ');
249        if(prefix==-1) {
250            textBuffer.set(localName);
251            textBuffer.write(this);
252        } else
253            writeName(prefix,localName);
254        write(EQUALS);
255        doText(value,true);
256        write('\"');
257    }
258
259    public void endStartTag() throws IOException {
260        closeStartTagPending = true;
261    }
262
263    @Override
264    public void endTag(Name name) throws IOException {
265        if(closeStartTagPending) {
266            write(EMPTY_TAG);
267            closeStartTagPending = false;
268        } else {
269            write(CLOSE_TAG);
270            writeName(name);
271            write('>');
272        }
273    }
274
275    public void endTag(int prefix, String localName) throws IOException {
276        if(closeStartTagPending) {
277            write(EMPTY_TAG);
278            closeStartTagPending = false;
279        } else {
280            write(CLOSE_TAG);
281            writeName(prefix,localName);
282            write('>');
283        }
284    }
285
286    public void text(String value, boolean needSP) throws IOException {
287        closeStartTag();
288        if(needSP)
289            write(' ');
290        doText(value,false);
291    }
292
293    public void text(Pcdata value, boolean needSP) throws IOException {
294        closeStartTag();
295        if(needSP)
296            write(' ');
297        value.writeTo(this);
298    }
299
300    private void doText(String value,boolean isAttribute) throws IOException {
301        if (escapeHandler != null) {
302            StringWriter sw = new StringWriter();
303            escapeHandler.escape(value.toCharArray(), 0, value.length(), isAttribute, sw);
304            textBuffer.set(sw.toString());
305        } else {
306            textBuffer.setEscape(value, isAttribute);
307        }
308        textBuffer.write(this);
309    }
310
311    public final void text(int value) throws IOException {
312        closeStartTag();
313        /*
314         * TODO
315         * Change to use the octet buffer directly
316         */
317
318        // max is -2147483648 and 11 digits
319        boolean minus = (value<0);
320        textBuffer.ensureSize(11);
321        byte[] buf = textBuffer.buf;
322        int idx = 11;
323
324        do {
325            int r = value%10;
326            if(r<0) r = -r;
327            buf[--idx] = (byte)('0'|r);    // really measn 0x30+r but 0<=r<10, so bit-OR would do.
328            value /= 10;
329        } while(value!=0);
330
331        if(minus)   buf[--idx] = (byte)'-';
332
333        write(buf,idx,11-idx);
334    }
335
336    /**
337     * Writes the given byte[] as base64 encoded binary to the output.
338     *
339     * <p>
340     * Being defined on this class allows this method to access the buffer directly,
341     * which translates to a better performance.
342     */
343    public void text(byte[] data, int dataLen) throws IOException {
344        closeStartTag();
345
346        int start = 0;
347
348        while(dataLen>0) {
349            // how many bytes (in data) can we write without overflowing the buffer?
350            int batchSize = Math.min(((octetBuffer.length-octetBufferIndex)/4)*3,dataLen);
351
352            // write the batch
353            octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex);
354
355            if(batchSize<dataLen)
356                flushBuffer();
357
358            start += batchSize;
359            dataLen -= batchSize;
360
361        }
362    }
363
364//
365//
366// series of the write method that places bytes to the output
367// (by doing some buffering internal to this class)
368//
369
370    /**
371     * Writes one byte directly into the buffer.
372     *
373     * <p>
374     * This method can be used somewhat like the {@code text} method,
375     * but it doesn't perform character escaping.
376     */
377    public final void write(int i) throws IOException {
378        if (octetBufferIndex < octetBuffer.length) {
379            octetBuffer[octetBufferIndex++] = (byte)i;
380        } else {
381            out.write(octetBuffer);
382            octetBufferIndex = 1;
383            octetBuffer[0] = (byte)i;
384        }
385    }
386
387    protected final void write(byte[] b) throws IOException {
388        write(b, 0,  b.length);
389    }
390
391    protected final void write(byte[] b, int start, int length) throws IOException {
392        if ((octetBufferIndex + length) < octetBuffer.length) {
393            System.arraycopy(b, start, octetBuffer, octetBufferIndex, length);
394            octetBufferIndex += length;
395        } else {
396            out.write(octetBuffer, 0, octetBufferIndex);
397            out.write(b, start, length);
398            octetBufferIndex = 0;
399        }
400    }
401
402    protected final void flushBuffer() throws IOException {
403        out.write(octetBuffer, 0, octetBufferIndex);
404        octetBufferIndex = 0;
405    }
406
407    static byte[] toBytes(String s) {
408        byte[] buf = new byte[s.length()];
409        for( int i=s.length()-1; i>=0; i-- )
410            buf[i] = (byte)s.charAt(i);
411        return buf;
412    }
413
414    // per instance copy to prevent an attack where malicious OutputStream
415    // rewrites the byte array.
416    private final byte[] XMLNS_EQUALS = _XMLNS_EQUALS.clone();
417    private final byte[] XMLNS_COLON = _XMLNS_COLON.clone();
418    private final byte[] EQUALS = _EQUALS.clone();
419    private final byte[] CLOSE_TAG = _CLOSE_TAG.clone();
420    private final byte[] EMPTY_TAG = _EMPTY_TAG.clone();
421    private final byte[] XML_DECL = _XML_DECL.clone();
422
423    // masters
424    private static final byte[] _XMLNS_EQUALS = toBytes(" xmlns=\"");
425    private static final byte[] _XMLNS_COLON = toBytes(" xmlns:");
426    private static final byte[] _EQUALS = toBytes("=\"");
427    private static final byte[] _CLOSE_TAG = toBytes("</");
428    private static final byte[] _EMPTY_TAG = toBytes("/>");
429    private static final byte[] _XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
430
431    // no need to copy
432    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
433}
434