blob: e549b68c014a67ba448d71a2ba1691665a9a501d [file] [log] [blame]
/*
* Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.bind.v2.runtime.output;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import javax.xml.stream.XMLStreamException;
import com.sun.xml.internal.bind.DatatypeConverterImpl;
import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
import com.sun.xml.internal.bind.v2.runtime.Name;
import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl;
import org.xml.sax.SAXException;
/**
* {@link XmlOutput} implementation specialized for UTF-8.
*
* @author Kohsuke Kawaguchi
* @author Paul Sandoz
*/
public class UTF8XmlOutput extends XmlOutputAbstractImpl {
protected final OutputStream out;
/** prefixes encoded. */
private Encoded[] prefixes = new Encoded[8];
/**
* Of the {@link #prefixes}, number of filled entries.
* This is almost the same as {@link NamespaceContextImpl#count()},
* except that it allows us to handle contextual in-scope namespace bindings correctly.
*/
private int prefixCount;
/** local names encoded in UTF-8. All entries are pre-filled. */
private final Encoded[] localNames;
/** Temporary buffer used to encode text. */
/*
* TODO
* The textBuffer could write directly to the _octetBuffer
* when encoding a string if Encoder is modified.
* This will avoid an additional memory copy.
*/
private final Encoded textBuffer = new Encoded();
/** Buffer of octets for writing. */
// TODO: Obtain buffer size from property on the JAXB context
protected final byte[] octetBuffer = new byte[1024];
/** Index in buffer to write to. */
protected int octetBufferIndex;
/**
* Set to true to indicate that we need to write '>'
* to close a start tag. Deferring the write of this char
* allows us to write "/>" for empty elements.
*/
protected boolean closeStartTagPending = false;
/**
* @see MarshallerImpl#header
*/
private String header;
private CharacterEscapeHandler escapeHandler = null;
/**
*
* @param localNames
* local names encoded in UTF-8.
*/
public UTF8XmlOutput(OutputStream out, Encoded[] localNames, CharacterEscapeHandler escapeHandler) {
this.out = out;
this.localNames = localNames;
for( int i=0; i<prefixes.length; i++ )
prefixes[i] = new Encoded();
this.escapeHandler = escapeHandler;
}
public void setHeader(String header) {
this.header = header;
}
@Override
public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException {
super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext);
octetBufferIndex = 0;
if(!fragment) {
write(XML_DECL);
}
if(header!=null) {
textBuffer.set(header);
textBuffer.write(this);
}
}
@Override
public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
flushBuffer();
super.endDocument(fragment);
}
/**
* Writes '>' to close the start tag, if necessary.
*/
protected final void closeStartTag() throws IOException {
if(closeStartTagPending) {
write('>');
closeStartTagPending = false;
}
}
public void beginStartTag(int prefix, String localName) throws IOException {
closeStartTag();
int base= pushNsDecls();
write('<');
writeName(prefix,localName);
writeNsDecls(base);
}
@Override
public void beginStartTag(Name name) throws IOException {
closeStartTag();
int base = pushNsDecls();
write('<');
writeName(name);
writeNsDecls(base);
}
private int pushNsDecls() {
int total = nsContext.count();
NamespaceContextImpl.Element ns = nsContext.getCurrent();
if(total > prefixes.length) {
// reallocate
int m = Math.max(total,prefixes.length*2);
Encoded[] buf = new Encoded[m];
System.arraycopy(prefixes,0,buf,0,prefixes.length);
for( int i=prefixes.length; i<buf.length; i++ )
buf[i] = new Encoded();
prefixes = buf;
}
int base = Math.min(prefixCount,ns.getBase());
int size = nsContext.count();
for( int i=base; i<size; i++ ) {
String p = nsContext.getPrefix(i);
Encoded e = prefixes[i];
if(p.length()==0) {
e.buf = EMPTY_BYTE_ARRAY;
e.len = 0;
} else {
e.set(p);
e.append(':');
}
}
prefixCount = size;
return base;
}
protected void writeNsDecls(int base) throws IOException {
NamespaceContextImpl.Element ns = nsContext.getCurrent();
int size = nsContext.count();
for( int i=ns.getBase(); i<size; i++ )
writeNsDecl(i);
}
/**
* Writes a single namespace declaration for the specified prefix.
*/
protected final void writeNsDecl(int prefixIndex) throws IOException {
String p = nsContext.getPrefix(prefixIndex);
if(p.length()==0) {
if(nsContext.getCurrent().isRootElement()
&& nsContext.getNamespaceURI(prefixIndex).length()==0)
return; // no point in declaring xmlns="" on the root element
write(XMLNS_EQUALS);
} else {
Encoded e = prefixes[prefixIndex];
write(XMLNS_COLON);
write(e.buf,0,e.len-1); // skip the trailing ':'
write(EQUALS);
}
doText(nsContext.getNamespaceURI(prefixIndex),true);
write('\"');
}
private void writePrefix(int prefix) throws IOException {
prefixes[prefix].write(this);
}
private void writeName(Name name) throws IOException {
writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]);
localNames[name.localNameIndex].write(this);
}
private void writeName(int prefix, String localName) throws IOException {
writePrefix(prefix);
textBuffer.set(localName);
textBuffer.write(this);
}
@Override
public void attribute(Name name, String value) throws IOException {
write(' ');
if(name.nsUriIndex==-1) {
localNames[name.localNameIndex].write(this);
} else
writeName(name);
write(EQUALS);
doText(value,true);
write('\"');
}
public void attribute(int prefix, String localName, String value) throws IOException {
write(' ');
if(prefix==-1) {
textBuffer.set(localName);
textBuffer.write(this);
} else
writeName(prefix,localName);
write(EQUALS);
doText(value,true);
write('\"');
}
public void endStartTag() throws IOException {
closeStartTagPending = true;
}
@Override
public void endTag(Name name) throws IOException {
if(closeStartTagPending) {
write(EMPTY_TAG);
closeStartTagPending = false;
} else {
write(CLOSE_TAG);
writeName(name);
write('>');
}
}
public void endTag(int prefix, String localName) throws IOException {
if(closeStartTagPending) {
write(EMPTY_TAG);
closeStartTagPending = false;
} else {
write(CLOSE_TAG);
writeName(prefix,localName);
write('>');
}
}
public void text(String value, boolean needSP) throws IOException {
closeStartTag();
if(needSP)
write(' ');
doText(value,false);
}
public void text(Pcdata value, boolean needSP) throws IOException {
closeStartTag();
if(needSP)
write(' ');
value.writeTo(this);
}
private void doText(String value,boolean isAttribute) throws IOException {
if (escapeHandler != null) {
StringWriter sw = new StringWriter();
escapeHandler.escape(value.toCharArray(), 0, value.length(), isAttribute, sw);
textBuffer.set(sw.toString());
} else {
textBuffer.setEscape(value, isAttribute);
}
textBuffer.write(this);
}
public final void text(int value) throws IOException {
closeStartTag();
/*
* TODO
* Change to use the octet buffer directly
*/
// max is -2147483648 and 11 digits
boolean minus = (value<0);
textBuffer.ensureSize(11);
byte[] buf = textBuffer.buf;
int idx = 11;
do {
int r = value%10;
if(r<0) r = -r;
buf[--idx] = (byte)('0'|r); // really measn 0x30+r but 0<=r<10, so bit-OR would do.
value /= 10;
} while(value!=0);
if(minus) buf[--idx] = (byte)'-';
write(buf,idx,11-idx);
}
/**
* Writes the given byte[] as base64 encoded binary to the output.
*
* <p>
* Being defined on this class allows this method to access the buffer directly,
* which translates to a better performance.
*/
public void text(byte[] data, int dataLen) throws IOException {
closeStartTag();
int start = 0;
while(dataLen>0) {
// how many bytes (in data) can we write without overflowing the buffer?
int batchSize = Math.min(((octetBuffer.length-octetBufferIndex)/4)*3,dataLen);
// write the batch
octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex);
if(batchSize<dataLen)
flushBuffer();
start += batchSize;
dataLen -= batchSize;
}
}
//
//
// series of the write method that places bytes to the output
// (by doing some buffering internal to this class)
//
/**
* Writes one byte directly into the buffer.
*
* <p>
* This method can be used somewhat like the {@code text} method,
* but it doesn't perform character escaping.
*/
public final void write(int i) throws IOException {
if (octetBufferIndex < octetBuffer.length) {
octetBuffer[octetBufferIndex++] = (byte)i;
} else {
out.write(octetBuffer);
octetBufferIndex = 1;
octetBuffer[0] = (byte)i;
}
}
protected final void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
protected final void write(byte[] b, int start, int length) throws IOException {
if ((octetBufferIndex + length) < octetBuffer.length) {
System.arraycopy(b, start, octetBuffer, octetBufferIndex, length);
octetBufferIndex += length;
} else {
out.write(octetBuffer, 0, octetBufferIndex);
out.write(b, start, length);
octetBufferIndex = 0;
}
}
protected final void flushBuffer() throws IOException {
out.write(octetBuffer, 0, octetBufferIndex);
octetBufferIndex = 0;
}
static byte[] toBytes(String s) {
byte[] buf = new byte[s.length()];
for( int i=s.length()-1; i>=0; i-- )
buf[i] = (byte)s.charAt(i);
return buf;
}
// per instance copy to prevent an attack where malicious OutputStream
// rewrites the byte array.
private final byte[] XMLNS_EQUALS = _XMLNS_EQUALS.clone();
private final byte[] XMLNS_COLON = _XMLNS_COLON.clone();
private final byte[] EQUALS = _EQUALS.clone();
private final byte[] CLOSE_TAG = _CLOSE_TAG.clone();
private final byte[] EMPTY_TAG = _EMPTY_TAG.clone();
private final byte[] XML_DECL = _XML_DECL.clone();
// masters
private static final byte[] _XMLNS_EQUALS = toBytes(" xmlns=\"");
private static final byte[] _XMLNS_COLON = toBytes(" xmlns:");
private static final byte[] _EQUALS = toBytes("=\"");
private static final byte[] _CLOSE_TAG = toBytes("</");
private static final byte[] _EMPTY_TAG = toBytes("/>");
private static final byte[] _XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
// no need to copy
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
}