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