src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/output/UTF8XmlOutput.java

changeset 0
373ffda63c9a
equal deleted inserted replaced
-1:000000000000 0:373ffda63c9a
1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.xml.internal.bind.v2.runtime.output;
27
28 import java.io.IOException;
29 import java.io.OutputStream;
30
31 import java.io.StringWriter;
32 import javax.xml.stream.XMLStreamException;
33
34 import com.sun.xml.internal.bind.DatatypeConverterImpl;
35 import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
36 import com.sun.xml.internal.bind.v2.runtime.Name;
37 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
38 import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl;
39
40 import org.xml.sax.SAXException;
41
42 /**
43 * {@link XmlOutput} implementation specialized for UTF-8.
44 *
45 * @author Kohsuke Kawaguchi
46 * @author Paul Sandoz
47 */
48 public 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 '>'
82 * to close a start tag. Deferring the write of this char
83 * allows us to write "/>" 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 '>' 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 }

mercurial