Sat, 01 Dec 2007 00:00:00 +0000
Initial load
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 */
26 package com.sun.xml.internal.bind.v2.runtime.output;
28 import java.io.IOException;
29 import java.io.OutputStream;
31 import javax.xml.stream.XMLStreamException;
33 import com.sun.xml.internal.bind.DatatypeConverterImpl;
34 import com.sun.xml.internal.bind.v2.runtime.Name;
35 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
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 *
86 * @param localNames
87 * local names encoded in UTF-8.
88 */
89 public UTF8XmlOutput(OutputStream out, Encoded[] localNames) {
90 this.out = out;
91 this.localNames = localNames;
92 for( int i=0; i<prefixes.length; i++ )
93 prefixes[i] = new Encoded();
94 }
96 @Override
97 public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException {
98 super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext);
100 octetBufferIndex = 0;
101 if(!fragment) {
102 write(XML_DECL);
103 }
104 }
106 public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
107 flushBuffer();
108 super.endDocument(fragment);
109 }
111 /**
112 * Writes '>' to close the start tag, if necessary.
113 */
114 protected final void closeStartTag() throws IOException {
115 if(closeStartTagPending) {
116 write('>');
117 closeStartTagPending = false;
118 }
119 }
121 public void beginStartTag(int prefix, String localName) throws IOException {
122 closeStartTag();
123 int base= pushNsDecls();
124 write('<');
125 writeName(prefix,localName);
126 writeNsDecls(base);
127 }
129 public void beginStartTag(Name name) throws IOException {
130 closeStartTag();
131 int base = pushNsDecls();
132 write('<');
133 writeName(name);
134 writeNsDecls(base);
135 }
137 private int pushNsDecls() {
138 int total = nsContext.count();
139 NamespaceContextImpl.Element ns = nsContext.getCurrent();
141 if(total > prefixes.length) {
142 // reallocate
143 int m = Math.max(total,prefixes.length*2);
144 Encoded[] buf = new Encoded[m];
145 System.arraycopy(prefixes,0,buf,0,prefixes.length);
146 for( int i=prefixes.length; i<buf.length; i++ )
147 buf[i] = new Encoded();
148 prefixes = buf;
149 }
151 int base = Math.min(prefixCount,ns.getBase());
152 int size = nsContext.count();
153 for( int i=base; i<size; i++ ) {
154 String p = nsContext.getPrefix(i);
156 Encoded e = prefixes[i];
158 if(p.length()==0) {
159 e.buf = EMPTY_BYTE_ARRAY;
160 e.len = 0;
161 } else {
162 e.set(p);
163 e.append(':');
164 }
165 }
166 prefixCount = size;
167 return base;
168 }
170 protected void writeNsDecls(int base) throws IOException {
171 NamespaceContextImpl.Element ns = nsContext.getCurrent();
172 int size = nsContext.count();
174 for( int i=ns.getBase(); i<size; i++ )
175 writeNsDecl(i);
176 }
178 /**
179 * Writes a single namespace declaration for the specified prefix.
180 */
181 protected final void writeNsDecl(int prefixIndex) throws IOException {
182 String p = nsContext.getPrefix(prefixIndex);
184 if(p.length()==0) {
185 if(nsContext.getCurrent().isRootElement()
186 && nsContext.getNamespaceURI(prefixIndex).length()==0)
187 return; // no point in declaring xmlns="" on the root element
188 write(XMLNS_EQUALS);
189 } else {
190 Encoded e = prefixes[prefixIndex];
191 write(XMLNS_COLON);
192 write(e.buf,0,e.len-1); // skip the trailing ':'
193 write(EQUALS);
194 }
195 doText(nsContext.getNamespaceURI(prefixIndex),true);
196 write('\"');
197 }
199 private void writePrefix(int prefix) throws IOException {
200 prefixes[prefix].write(this);
201 }
203 private void writeName(Name name) throws IOException {
204 writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
205 localNames[name.localNameIndex].write(this);
206 }
208 private void writeName(int prefix, String localName) throws IOException {
209 writePrefix(prefix);
210 textBuffer.set(localName);
211 textBuffer.write(this);
212 }
214 @Override
215 public void attribute(Name name, String value) throws IOException {
216 write(' ');
217 if(name.nsUriIndex==-1) {
218 localNames[name.localNameIndex].write(this);
219 } else
220 writeName(name);
221 write(EQUALS);
222 doText(value,true);
223 write('\"');
224 }
226 public void attribute(int prefix, String localName, String value) throws IOException {
227 write(' ');
228 if(prefix==-1) {
229 textBuffer.set(localName);
230 textBuffer.write(this);
231 } else
232 writeName(prefix,localName);
233 write(EQUALS);
234 doText(value,true);
235 write('\"');
236 }
238 public void endStartTag() throws IOException {
239 closeStartTagPending = true;
240 }
242 @Override
243 public void endTag(Name name) throws IOException {
244 if(closeStartTagPending) {
245 write(EMPTY_TAG);
246 closeStartTagPending = false;
247 } else {
248 write(CLOSE_TAG);
249 writeName(name);
250 write('>');
251 }
252 }
254 public void endTag(int prefix, String localName) throws IOException {
255 if(closeStartTagPending) {
256 write(EMPTY_TAG);
257 closeStartTagPending = false;
258 } else {
259 write(CLOSE_TAG);
260 writeName(prefix,localName);
261 write('>');
262 }
263 }
265 public void text(String value, boolean needSP) throws IOException {
266 closeStartTag();
267 if(needSP)
268 write(' ');
269 doText(value,false);
270 }
272 public void text(Pcdata value, boolean needSP) throws IOException {
273 closeStartTag();
274 if(needSP)
275 write(' ');
276 value.writeTo(this);
277 }
279 private void doText(String value,boolean isAttribute) throws IOException {
280 textBuffer.setEscape(value,isAttribute);
281 textBuffer.write(this);
282 }
284 public final void text(int value) throws IOException {
285 closeStartTag();
286 /*
287 * TODO
288 * Change to use the octet buffer directly
289 */
291 // max is -2147483648 and 11 digits
292 boolean minus = (value<0);
293 textBuffer.ensureSize(11);
294 byte[] buf = textBuffer.buf;
295 int idx = 11;
297 do {
298 int r = value%10;
299 if(r<0) r = -r;
300 buf[--idx] = (byte)('0'|r); // really measn 0x30+r but 0<=r<10, so bit-OR would do.
301 value /= 10;
302 } while(value!=0);
304 if(minus) buf[--idx] = (byte)'-';
306 write(buf,idx,11-idx);
307 }
309 /**
310 * Writes the given byte[] as base64 encoded binary to the output.
311 *
312 * <p>
313 * Being defined on this class allows this method to access the buffer directly,
314 * which translates to a better performance.
315 */
316 public void text(byte[] data, int dataLen) throws IOException {
317 closeStartTag();
319 int start = 0;
321 while(dataLen>0) {
322 // how many bytes (in data) can we write without overflowing the buffer?
323 int batchSize = Math.min(((octetBuffer.length-octetBufferIndex)/4)*3,dataLen);
325 // write the batch
326 octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex);
328 if(batchSize<dataLen)
329 flushBuffer();
331 start += batchSize;
332 dataLen -= batchSize;
334 }
335 }
337 //
338 //
339 // series of the write method that places bytes to the output
340 // (by doing some buffering internal to this class)
341 //
343 /**
344 * Writes one byte directly into the buffer.
345 *
346 * <p>
347 * This method can be used somewhat like the {@code text} method,
348 * but it doesn't perform character escaping.
349 */
350 public final void write(int i) throws IOException {
351 if (octetBufferIndex < octetBuffer.length) {
352 octetBuffer[octetBufferIndex++] = (byte)i;
353 } else {
354 out.write(octetBuffer);
355 octetBufferIndex = 1;
356 octetBuffer[0] = (byte)i;
357 }
358 }
360 protected final void write(byte[] b) throws IOException {
361 write(b, 0, b.length);
362 }
364 protected final void write(byte[] b, int start, int length) throws IOException {
365 if ((octetBufferIndex + length) < octetBuffer.length) {
366 System.arraycopy(b, start, octetBuffer, octetBufferIndex, length);
367 octetBufferIndex += length;
368 } else {
369 out.write(octetBuffer, 0, octetBufferIndex);
370 out.write(b, start, length);
371 octetBufferIndex = 0;
372 }
373 }
375 protected final void flushBuffer() throws IOException {
376 out.write(octetBuffer, 0, octetBufferIndex);
377 octetBufferIndex = 0;
378 }
380 public void flush() throws IOException {
381 flushBuffer();
382 out.flush();
383 }
387 static byte[] toBytes(String s) {
388 byte[] buf = new byte[s.length()];
389 for( int i=s.length()-1; i>=0; i-- )
390 buf[i] = (byte)s.charAt(i);
391 return buf;
392 }
394 private static final byte[] XMLNS_EQUALS = toBytes(" xmlns=\"");
395 private static final byte[] XMLNS_COLON = toBytes(" xmlns:");
396 private static final byte[] EQUALS = toBytes("=\"");
397 private static final byte[] CLOSE_TAG = toBytes("</");
398 private static final byte[] EMPTY_TAG = toBytes("/>");
399 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
400 private static final byte[] XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
401 }