001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.bind.v2.runtime.output;
027:
028: import java.io.IOException;
029: import java.io.OutputStream;
030:
031: import javax.xml.stream.XMLStreamException;
032:
033: import com.sun.xml.internal.bind.DatatypeConverterImpl;
034: import com.sun.xml.internal.bind.v2.runtime.Name;
035: import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
036:
037: import org.xml.sax.SAXException;
038:
039: /**
040: * {@link XmlOutput} implementation specialized for UTF-8.
041: *
042: * @author Kohsuke Kawaguchi
043: * @author Paul Sandoz
044: */
045: public class UTF8XmlOutput extends XmlOutputAbstractImpl {
046: protected final OutputStream out;
047:
048: /** prefixes encoded. */
049: private Encoded[] prefixes = new Encoded[8];
050:
051: /**
052: * Of the {@link #prefixes}, number of filled entries.
053: * This is almost the same as {@link NamespaceContextImpl#count()},
054: * except that it allows us to handle contextual in-scope namespace bindings correctly.
055: */
056: private int prefixCount;
057:
058: /** local names encoded in UTF-8. All entries are pre-filled. */
059: private final Encoded[] localNames;
060:
061: /** Temporary buffer used to encode text. */
062: /*
063: * TODO
064: * The textBuffer could write directly to the _octetBuffer
065: * when encoding a string if Encoder is modified.
066: * This will avoid an additional memory copy.
067: */
068: private final Encoded textBuffer = new Encoded();
069:
070: /** Buffer of octets for writing. */
071: // TODO: Obtain buffer size from property on the JAXB context
072: protected final byte[] octetBuffer = new byte[1024];
073:
074: /** Index in buffer to write to. */
075: protected int octetBufferIndex;
076:
077: /**
078: * Set to true to indicate that we need to write '>'
079: * to close a start tag. Deferring the write of this char
080: * allows us to write "/>" for empty elements.
081: */
082: protected boolean closeStartTagPending = false;
083:
084: /**
085: *
086: * @param localNames
087: * local names encoded in UTF-8.
088: */
089: public UTF8XmlOutput(OutputStream out, Encoded[] localNames) {
090: this .out = out;
091: this .localNames = localNames;
092: for (int i = 0; i < prefixes.length; i++)
093: prefixes[i] = new Encoded();
094: }
095:
096: @Override
097: public void startDocument(XMLSerializer serializer,
098: boolean fragment, int[] nsUriIndex2prefixIndex,
099: NamespaceContextImpl nsContext) throws IOException,
100: SAXException, XMLStreamException {
101: super .startDocument(serializer, fragment,
102: nsUriIndex2prefixIndex, nsContext);
103:
104: octetBufferIndex = 0;
105: if (!fragment) {
106: write(XML_DECL);
107: }
108: }
109:
110: public void endDocument(boolean fragment) throws IOException,
111: SAXException, XMLStreamException {
112: flushBuffer();
113: super .endDocument(fragment);
114: }
115:
116: /**
117: * Writes '>' to close the start tag, if necessary.
118: */
119: protected final void closeStartTag() throws IOException {
120: if (closeStartTagPending) {
121: write('>');
122: closeStartTagPending = false;
123: }
124: }
125:
126: public void beginStartTag(int prefix, String localName)
127: throws IOException {
128: closeStartTag();
129: int base = pushNsDecls();
130: write('<');
131: writeName(prefix, localName);
132: writeNsDecls(base);
133: }
134:
135: public void beginStartTag(Name name) throws IOException {
136: closeStartTag();
137: int base = pushNsDecls();
138: write('<');
139: writeName(name);
140: writeNsDecls(base);
141: }
142:
143: private int pushNsDecls() {
144: int total = nsContext.count();
145: NamespaceContextImpl.Element ns = nsContext.getCurrent();
146:
147: if (total > prefixes.length) {
148: // reallocate
149: int m = Math.max(total, prefixes.length * 2);
150: Encoded[] buf = new Encoded[m];
151: System.arraycopy(prefixes, 0, buf, 0, prefixes.length);
152: for (int i = prefixes.length; i < buf.length; i++)
153: buf[i] = new Encoded();
154: prefixes = buf;
155: }
156:
157: int base = Math.min(prefixCount, ns.getBase());
158: int size = nsContext.count();
159: for (int i = base; i < size; i++) {
160: String p = nsContext.getPrefix(i);
161:
162: Encoded e = prefixes[i];
163:
164: if (p.length() == 0) {
165: e.buf = EMPTY_BYTE_ARRAY;
166: e.len = 0;
167: } else {
168: e.set(p);
169: e.append(':');
170: }
171: }
172: prefixCount = size;
173: return base;
174: }
175:
176: protected void writeNsDecls(int base) throws IOException {
177: NamespaceContextImpl.Element ns = nsContext.getCurrent();
178: int size = nsContext.count();
179:
180: for (int i = ns.getBase(); i < size; i++)
181: writeNsDecl(i);
182: }
183:
184: /**
185: * Writes a single namespace declaration for the specified prefix.
186: */
187: protected final void writeNsDecl(int prefixIndex)
188: throws IOException {
189: String p = nsContext.getPrefix(prefixIndex);
190:
191: if (p.length() == 0) {
192: if (nsContext.getCurrent().isRootElement()
193: && nsContext.getNamespaceURI(prefixIndex).length() == 0)
194: return; // no point in declaring xmlns="" on the root element
195: write(XMLNS_EQUALS);
196: } else {
197: Encoded e = prefixes[prefixIndex];
198: write(XMLNS_COLON);
199: write(e.buf, 0, e.len - 1); // skip the trailing ':'
200: write(EQUALS);
201: }
202: doText(nsContext.getNamespaceURI(prefixIndex), true);
203: write('\"');
204: }
205:
206: private void writePrefix(int prefix) throws IOException {
207: prefixes[prefix].write(this );
208: }
209:
210: private void writeName(Name name) throws IOException {
211: writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
212: localNames[name.localNameIndex].write(this );
213: }
214:
215: private void writeName(int prefix, String localName)
216: throws IOException {
217: writePrefix(prefix);
218: textBuffer.set(localName);
219: textBuffer.write(this );
220: }
221:
222: @Override
223: public void attribute(Name name, String value) throws IOException {
224: write(' ');
225: if (name.nsUriIndex == -1) {
226: localNames[name.localNameIndex].write(this );
227: } else
228: writeName(name);
229: write(EQUALS);
230: doText(value, true);
231: write('\"');
232: }
233:
234: public void attribute(int prefix, String localName, String value)
235: throws IOException {
236: write(' ');
237: if (prefix == -1) {
238: textBuffer.set(localName);
239: textBuffer.write(this );
240: } else
241: writeName(prefix, localName);
242: write(EQUALS);
243: doText(value, true);
244: write('\"');
245: }
246:
247: public void endStartTag() throws IOException {
248: closeStartTagPending = true;
249: }
250:
251: @Override
252: public void endTag(Name name) throws IOException {
253: if (closeStartTagPending) {
254: write(EMPTY_TAG);
255: closeStartTagPending = false;
256: } else {
257: write(CLOSE_TAG);
258: writeName(name);
259: write('>');
260: }
261: }
262:
263: public void endTag(int prefix, String localName) throws IOException {
264: if (closeStartTagPending) {
265: write(EMPTY_TAG);
266: closeStartTagPending = false;
267: } else {
268: write(CLOSE_TAG);
269: writeName(prefix, localName);
270: write('>');
271: }
272: }
273:
274: public void text(String value, boolean needSP) throws IOException {
275: closeStartTag();
276: if (needSP)
277: write(' ');
278: doText(value, false);
279: }
280:
281: public void text(Pcdata value, boolean needSP) throws IOException {
282: closeStartTag();
283: if (needSP)
284: write(' ');
285: value.writeTo(this );
286: }
287:
288: private void doText(String value, boolean isAttribute)
289: throws IOException {
290: textBuffer.setEscape(value, isAttribute);
291: textBuffer.write(this );
292: }
293:
294: public final void text(int value) throws IOException {
295: closeStartTag();
296: /*
297: * TODO
298: * Change to use the octet buffer directly
299: */
300:
301: // max is -2147483648 and 11 digits
302: boolean minus = (value < 0);
303: textBuffer.ensureSize(11);
304: byte[] buf = textBuffer.buf;
305: int idx = 11;
306:
307: do {
308: int r = value % 10;
309: if (r < 0)
310: r = -r;
311: buf[--idx] = (byte) ('0' | r); // really measn 0x30+r but 0<=r<10, so bit-OR would do.
312: value /= 10;
313: } while (value != 0);
314:
315: if (minus)
316: buf[--idx] = (byte) '-';
317:
318: write(buf, idx, 11 - idx);
319: }
320:
321: /**
322: * Writes the given byte[] as base64 encoded binary to the output.
323: *
324: * <p>
325: * Being defined on this class allows this method to access the buffer directly,
326: * which translates to a better performance.
327: */
328: public void text(byte[] data, int dataLen) throws IOException {
329: closeStartTag();
330:
331: int start = 0;
332:
333: while (dataLen > 0) {
334: // how many bytes (in data) can we write without overflowing the buffer?
335: int batchSize = Math.min(
336: ((octetBuffer.length - octetBufferIndex) / 4) * 3,
337: dataLen);
338:
339: // write the batch
340: octetBufferIndex = DatatypeConverterImpl
341: ._printBase64Binary(data, start, batchSize,
342: octetBuffer, octetBufferIndex);
343:
344: if (batchSize < dataLen)
345: flushBuffer();
346:
347: start += batchSize;
348: dataLen -= batchSize;
349:
350: }
351: }
352:
353: //
354: //
355: // series of the write method that places bytes to the output
356: // (by doing some buffering internal to this class)
357: //
358:
359: /**
360: * Writes one byte directly into the buffer.
361: *
362: * <p>
363: * This method can be used somewhat like the {@code text} method,
364: * but it doesn't perform character escaping.
365: */
366: public final void write(int i) throws IOException {
367: if (octetBufferIndex < octetBuffer.length) {
368: octetBuffer[octetBufferIndex++] = (byte) i;
369: } else {
370: out.write(octetBuffer);
371: octetBufferIndex = 1;
372: octetBuffer[0] = (byte) i;
373: }
374: }
375:
376: protected final void write(byte[] b) throws IOException {
377: write(b, 0, b.length);
378: }
379:
380: protected final void write(byte[] b, int start, int length)
381: throws IOException {
382: if ((octetBufferIndex + length) < octetBuffer.length) {
383: System.arraycopy(b, start, octetBuffer, octetBufferIndex,
384: length);
385: octetBufferIndex += length;
386: } else {
387: out.write(octetBuffer, 0, octetBufferIndex);
388: out.write(b, start, length);
389: octetBufferIndex = 0;
390: }
391: }
392:
393: protected final void flushBuffer() throws IOException {
394: out.write(octetBuffer, 0, octetBufferIndex);
395: octetBufferIndex = 0;
396: }
397:
398: public void flush() throws IOException {
399: flushBuffer();
400: out.flush();
401: }
402:
403: static byte[] toBytes(String s) {
404: byte[] buf = new byte[s.length()];
405: for (int i = s.length() - 1; i >= 0; i--)
406: buf[i] = (byte) s.charAt(i);
407: return buf;
408: }
409:
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[] EMPTY_BYTE_ARRAY = new byte[0];
416: private static final byte[] XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
417: }
|