001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.bind.v2.runtime.output;
038:
039: import java.io.IOException;
040: import java.io.OutputStream;
041:
042: import javax.xml.stream.XMLStreamException;
043:
044: import com.sun.xml.bind.DatatypeConverterImpl;
045: import com.sun.xml.bind.v2.runtime.Name;
046: import com.sun.xml.bind.v2.runtime.XMLSerializer;
047: import com.sun.xml.bind.v2.runtime.MarshallerImpl;
048:
049: import org.xml.sax.SAXException;
050:
051: /**
052: * {@link XmlOutput} implementation specialized for UTF-8.
053: *
054: * @author Kohsuke Kawaguchi
055: * @author Paul Sandoz
056: */
057: public class UTF8XmlOutput extends XmlOutputAbstractImpl {
058: protected final OutputStream out;
059:
060: /** prefixes encoded. */
061: private Encoded[] prefixes = new Encoded[8];
062:
063: /**
064: * Of the {@link #prefixes}, number of filled entries.
065: * This is almost the same as {@link NamespaceContextImpl#count()},
066: * except that it allows us to handle contextual in-scope namespace bindings correctly.
067: */
068: private int prefixCount;
069:
070: /** local names encoded in UTF-8. All entries are pre-filled. */
071: private final Encoded[] localNames;
072:
073: /** Temporary buffer used to encode text. */
074: /*
075: * TODO
076: * The textBuffer could write directly to the _octetBuffer
077: * when encoding a string if Encoder is modified.
078: * This will avoid an additional memory copy.
079: */
080: private final Encoded textBuffer = new Encoded();
081:
082: /** Buffer of octets for writing. */
083: // TODO: Obtain buffer size from property on the JAXB context
084: protected final byte[] octetBuffer = new byte[1024];
085:
086: /** Index in buffer to write to. */
087: protected int octetBufferIndex;
088:
089: /**
090: * Set to true to indicate that we need to write '>'
091: * to close a start tag. Deferring the write of this char
092: * allows us to write "/>" for empty elements.
093: */
094: protected boolean closeStartTagPending = false;
095:
096: /**
097: * @see MarshallerImpl#header
098: */
099: private String header;
100:
101: /**
102: *
103: * @param localNames
104: * local names encoded in UTF-8.
105: */
106: public UTF8XmlOutput(OutputStream out, Encoded[] localNames) {
107: this .out = out;
108: this .localNames = localNames;
109: for (int i = 0; i < prefixes.length; i++)
110: prefixes[i] = new Encoded();
111: }
112:
113: public void setHeader(String header) {
114: this .header = header;
115: }
116:
117: @Override
118: public void startDocument(XMLSerializer serializer,
119: boolean fragment, int[] nsUriIndex2prefixIndex,
120: NamespaceContextImpl nsContext) throws IOException,
121: SAXException, XMLStreamException {
122: super .startDocument(serializer, fragment,
123: nsUriIndex2prefixIndex, nsContext);
124:
125: octetBufferIndex = 0;
126: if (!fragment) {
127: write(XML_DECL);
128: }
129: if (header != null) {
130: textBuffer.set(header);
131: textBuffer.write(this );
132: }
133: }
134:
135: public void endDocument(boolean fragment) throws IOException,
136: SAXException, XMLStreamException {
137: flushBuffer();
138: super .endDocument(fragment);
139: }
140:
141: /**
142: * Writes '>' to close the start tag, if necessary.
143: */
144: protected final void closeStartTag() throws IOException {
145: if (closeStartTagPending) {
146: write('>');
147: closeStartTagPending = false;
148: }
149: }
150:
151: public void beginStartTag(int prefix, String localName)
152: throws IOException {
153: closeStartTag();
154: int base = pushNsDecls();
155: write('<');
156: writeName(prefix, localName);
157: writeNsDecls(base);
158: }
159:
160: public void beginStartTag(Name name) throws IOException {
161: closeStartTag();
162: int base = pushNsDecls();
163: write('<');
164: writeName(name);
165: writeNsDecls(base);
166: }
167:
168: private int pushNsDecls() {
169: int total = nsContext.count();
170: NamespaceContextImpl.Element ns = nsContext.getCurrent();
171:
172: if (total > prefixes.length) {
173: // reallocate
174: int m = Math.max(total, prefixes.length * 2);
175: Encoded[] buf = new Encoded[m];
176: System.arraycopy(prefixes, 0, buf, 0, prefixes.length);
177: for (int i = prefixes.length; i < buf.length; i++)
178: buf[i] = new Encoded();
179: prefixes = buf;
180: }
181:
182: int base = Math.min(prefixCount, ns.getBase());
183: int size = nsContext.count();
184: for (int i = base; i < size; i++) {
185: String p = nsContext.getPrefix(i);
186:
187: Encoded e = prefixes[i];
188:
189: if (p.length() == 0) {
190: e.buf = EMPTY_BYTE_ARRAY;
191: e.len = 0;
192: } else {
193: e.set(p);
194: e.append(':');
195: }
196: }
197: prefixCount = size;
198: return base;
199: }
200:
201: protected void writeNsDecls(int base) throws IOException {
202: NamespaceContextImpl.Element ns = nsContext.getCurrent();
203: int size = nsContext.count();
204:
205: for (int i = ns.getBase(); i < size; i++)
206: writeNsDecl(i);
207: }
208:
209: /**
210: * Writes a single namespace declaration for the specified prefix.
211: */
212: protected final void writeNsDecl(int prefixIndex)
213: throws IOException {
214: String p = nsContext.getPrefix(prefixIndex);
215:
216: if (p.length() == 0) {
217: if (nsContext.getCurrent().isRootElement()
218: && nsContext.getNamespaceURI(prefixIndex).length() == 0)
219: return; // no point in declaring xmlns="" on the root element
220: write(XMLNS_EQUALS);
221: } else {
222: Encoded e = prefixes[prefixIndex];
223: write(XMLNS_COLON);
224: write(e.buf, 0, e.len - 1); // skip the trailing ':'
225: write(EQUALS);
226: }
227: doText(nsContext.getNamespaceURI(prefixIndex), true);
228: write('\"');
229: }
230:
231: private void writePrefix(int prefix) throws IOException {
232: prefixes[prefix].write(this );
233: }
234:
235: private void writeName(Name name) throws IOException {
236: writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
237: localNames[name.localNameIndex].write(this );
238: }
239:
240: private void writeName(int prefix, String localName)
241: throws IOException {
242: writePrefix(prefix);
243: textBuffer.set(localName);
244: textBuffer.write(this );
245: }
246:
247: @Override
248: public void attribute(Name name, String value) throws IOException {
249: write(' ');
250: if (name.nsUriIndex == -1) {
251: localNames[name.localNameIndex].write(this );
252: } else
253: writeName(name);
254: write(EQUALS);
255: doText(value, true);
256: write('\"');
257: }
258:
259: public void attribute(int prefix, String localName, String value)
260: throws IOException {
261: write(' ');
262: if (prefix == -1) {
263: textBuffer.set(localName);
264: textBuffer.write(this );
265: } else
266: writeName(prefix, localName);
267: write(EQUALS);
268: doText(value, true);
269: write('\"');
270: }
271:
272: public void endStartTag() throws IOException {
273: closeStartTagPending = true;
274: }
275:
276: @Override
277: public void endTag(Name name) throws IOException {
278: if (closeStartTagPending) {
279: write(EMPTY_TAG);
280: closeStartTagPending = false;
281: } else {
282: write(CLOSE_TAG);
283: writeName(name);
284: write('>');
285: }
286: }
287:
288: public void endTag(int prefix, String localName) throws IOException {
289: if (closeStartTagPending) {
290: write(EMPTY_TAG);
291: closeStartTagPending = false;
292: } else {
293: write(CLOSE_TAG);
294: writeName(prefix, localName);
295: write('>');
296: }
297: }
298:
299: public void text(String value, boolean needSP) throws IOException {
300: closeStartTag();
301: if (needSP)
302: write(' ');
303: doText(value, false);
304: }
305:
306: public void text(Pcdata value, boolean needSP) throws IOException {
307: closeStartTag();
308: if (needSP)
309: write(' ');
310: value.writeTo(this );
311: }
312:
313: private void doText(String value, boolean isAttribute)
314: throws IOException {
315: textBuffer.setEscape(value, isAttribute);
316: textBuffer.write(this );
317: }
318:
319: public final void text(int value) throws IOException {
320: closeStartTag();
321: /*
322: * TODO
323: * Change to use the octet buffer directly
324: */
325:
326: // max is -2147483648 and 11 digits
327: boolean minus = (value < 0);
328: textBuffer.ensureSize(11);
329: byte[] buf = textBuffer.buf;
330: int idx = 11;
331:
332: do {
333: int r = value % 10;
334: if (r < 0)
335: r = -r;
336: buf[--idx] = (byte) ('0' | r); // really measn 0x30+r but 0<=r<10, so bit-OR would do.
337: value /= 10;
338: } while (value != 0);
339:
340: if (minus)
341: buf[--idx] = (byte) '-';
342:
343: write(buf, idx, 11 - idx);
344: }
345:
346: /**
347: * Writes the given byte[] as base64 encoded binary to the output.
348: *
349: * <p>
350: * Being defined on this class allows this method to access the buffer directly,
351: * which translates to a better performance.
352: */
353: public void text(byte[] data, int dataLen) throws IOException {
354: closeStartTag();
355:
356: int start = 0;
357:
358: while (dataLen > 0) {
359: // how many bytes (in data) can we write without overflowing the buffer?
360: int batchSize = Math.min(
361: ((octetBuffer.length - octetBufferIndex) / 4) * 3,
362: dataLen);
363:
364: // write the batch
365: octetBufferIndex = DatatypeConverterImpl
366: ._printBase64Binary(data, start, batchSize,
367: octetBuffer, octetBufferIndex);
368:
369: if (batchSize < dataLen)
370: flushBuffer();
371:
372: start += batchSize;
373: dataLen -= batchSize;
374:
375: }
376: }
377:
378: //
379: //
380: // series of the write method that places bytes to the output
381: // (by doing some buffering internal to this class)
382: //
383:
384: /**
385: * Writes one byte directly into the buffer.
386: *
387: * <p>
388: * This method can be used somewhat like the {@code text} method,
389: * but it doesn't perform character escaping.
390: */
391: public final void write(int i) throws IOException {
392: if (octetBufferIndex < octetBuffer.length) {
393: octetBuffer[octetBufferIndex++] = (byte) i;
394: } else {
395: out.write(octetBuffer);
396: octetBufferIndex = 1;
397: octetBuffer[0] = (byte) i;
398: }
399: }
400:
401: protected final void write(byte[] b) throws IOException {
402: write(b, 0, b.length);
403: }
404:
405: protected final void write(byte[] b, int start, int length)
406: throws IOException {
407: if ((octetBufferIndex + length) < octetBuffer.length) {
408: System.arraycopy(b, start, octetBuffer, octetBufferIndex,
409: length);
410: octetBufferIndex += length;
411: } else {
412: out.write(octetBuffer, 0, octetBufferIndex);
413: out.write(b, start, length);
414: octetBufferIndex = 0;
415: }
416: }
417:
418: protected final void flushBuffer() throws IOException {
419: out.write(octetBuffer, 0, octetBufferIndex);
420: octetBufferIndex = 0;
421: }
422:
423: static byte[] toBytes(String s) {
424: byte[] buf = new byte[s.length()];
425: for (int i = s.length() - 1; i >= 0; i--)
426: buf[i] = (byte) s.charAt(i);
427: return buf;
428: }
429:
430: // per instance copy to prevent an attack where malicious OutputStream
431: // rewrites the byte array.
432: private final byte[] XMLNS_EQUALS = _XMLNS_EQUALS.clone();
433: private final byte[] XMLNS_COLON = _XMLNS_COLON.clone();
434: private final byte[] EQUALS = _EQUALS.clone();
435: private final byte[] CLOSE_TAG = _CLOSE_TAG.clone();
436: private final byte[] EMPTY_TAG = _EMPTY_TAG.clone();
437: private final byte[] XML_DECL = _XML_DECL.clone();
438:
439: // masters
440: private static final byte[] _XMLNS_EQUALS = toBytes(" xmlns=\"");
441: private static final byte[] _XMLNS_COLON = toBytes(" xmlns:");
442: private static final byte[] _EQUALS = toBytes("=\"");
443: private static final byte[] _CLOSE_TAG = toBytes("</");
444: private static final byte[] _EMPTY_TAG = toBytes("/>");
445: private static final byte[] _XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
446:
447: // no need to copy
448: private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
449: }
|