Mon, 20 Apr 2009 15:25:02 -0700
Merge
1 /*
2 * Copyright 2005-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;
27 import java.io.IOException;
28 import java.io.OutputStream;
30 import javax.xml.stream.XMLStreamException;
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;
35 import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl;
37 import org.xml.sax.SAXException;
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;
48 /** prefixes encoded. */
49 private Encoded[] prefixes = new Encoded[8];
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;
58 /** local names encoded in UTF-8. All entries are pre-filled. */
59 private final Encoded[] localNames;
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();
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];
74 /** Index in buffer to write to. */
75 protected int octetBufferIndex;
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;
84 /**
85 * @see MarshallerImpl#header
86 */
87 private String header;
89 /**
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 }
101 public void setHeader(String header) {
102 this.header = header;
103 }
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);
109 octetBufferIndex = 0;
110 if(!fragment) {
111 write(XML_DECL);
112 }
113 if(header!=null) {
114 textBuffer.set(header);
115 textBuffer.write(this);
116 }
117 }
119 public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
120 flushBuffer();
121 super.endDocument(fragment);
122 }
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 }
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 }
142 public void beginStartTag(Name name) throws IOException {
143 closeStartTag();
144 int base = pushNsDecls();
145 write('<');
146 writeName(name);
147 writeNsDecls(base);
148 }
150 private int pushNsDecls() {
151 int total = nsContext.count();
152 NamespaceContextImpl.Element ns = nsContext.getCurrent();
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 }
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);
169 Encoded e = prefixes[i];
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 }
183 protected void writeNsDecls(int base) throws IOException {
184 NamespaceContextImpl.Element ns = nsContext.getCurrent();
185 int size = nsContext.count();
187 for( int i=ns.getBase(); i<size; i++ )
188 writeNsDecl(i);
189 }
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);
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 }
212 private void writePrefix(int prefix) throws IOException {
213 prefixes[prefix].write(this);
214 }
216 private void writeName(Name name) throws IOException {
217 writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
218 localNames[name.localNameIndex].write(this);
219 }
221 private void writeName(int prefix, String localName) throws IOException {
222 writePrefix(prefix);
223 textBuffer.set(localName);
224 textBuffer.write(this);
225 }
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 }
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 }
251 public void endStartTag() throws IOException {
252 closeStartTagPending = true;
253 }
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 }
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 }
278 public void text(String value, boolean needSP) throws IOException {
279 closeStartTag();
280 if(needSP)
281 write(' ');
282 doText(value,false);
283 }
285 public void text(Pcdata value, boolean needSP) throws IOException {
286 closeStartTag();
287 if(needSP)
288 write(' ');
289 value.writeTo(this);
290 }
292 private void doText(String value,boolean isAttribute) throws IOException {
293 textBuffer.setEscape(value,isAttribute);
294 textBuffer.write(this);
295 }
297 public final void text(int value) throws IOException {
298 closeStartTag();
299 /*
300 * TODO
301 * Change to use the octet buffer directly
302 */
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;
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);
317 if(minus) buf[--idx] = (byte)'-';
319 write(buf,idx,11-idx);
320 }
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();
332 int start = 0;
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);
338 // write the batch
339 octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex);
341 if(batchSize<dataLen)
342 flushBuffer();
344 start += batchSize;
345 dataLen -= batchSize;
347 }
348 }
350 //
351 //
352 // series of the write method that places bytes to the output
353 // (by doing some buffering internal to this class)
354 //
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 }
373 protected final void write(byte[] b) throws IOException {
374 write(b, 0, b.length);
375 }
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 }
388 protected final void flushBuffer() throws IOException {
389 out.write(octetBuffer, 0, octetBufferIndex);
390 octetBufferIndex = 0;
391 }
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 }
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();
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\"?>");
417 // no need to copy
418 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
419 }