blob: 12ddb02cdd0ad5d5ff75d06a55f6d7694b8b6f54 [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 com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
import com.sun.xml.internal.bind.v2.runtime.Name;
import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
import javax.xml.stream.XMLStreamException;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
import com.sun.xml.internal.fastinfoset.EncodingConstants;
import com.sun.xml.internal.fastinfoset.stax.StAXDocumentSerializer;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.WeakHashMap;
import javax.xml.bind.JAXBContext;
import com.sun.xml.internal.org.jvnet.fastinfoset.VocabularyApplicationData;
import org.xml.sax.SAXException;
/**
* {@link XmlOutput} for {@link LowLevelStAXDocumentSerializer}.
* <p>
* This class is responsible for managing the indexing of elements, attributes
* and local names that are known to JAXB by way of the JAXBContext (generated
* from JAXB beans or schema). The pre-encoded UTF-8 representations of known
* local names are also utilized.
* <p>
* The lookup of elements, attributes and local names with respect to a context
* is very efficient. It relies on an incrementing base line so that look up is
* performed in O(1) time and only uses static memory. When the base line reaches
* a point where integer overflow will occur the arrays and base line are reset
* (such an event is rare and will have little impact on performance).
* <p>
* A weak map of JAXB contexts to optimized tables for attributes, elements and
* local names is utilized and stored on the LowLevel StAX serializer. Thus,
* optimized serializing can work other multiple serializing of JAXB beans using
* the same LowLevel StAX serializer instance. This approach works best when JAXB
* contexts are only created once per schema or JAXB beans (which is the recommended
* practice as the creation JAXB contexts are expensive, they are thread safe and
* can be reused).
*
* @author Paul.Sandoz@Sun.Com
*/
public final class FastInfosetStreamWriterOutput extends XMLStreamWriterOutput {
private final StAXDocumentSerializer fiout;
private final Encoded[] localNames;
private final TablesPerJAXBContext tables;
/**
* Holder for the optimzed element, attribute and
* local name tables.
*/
final static class TablesPerJAXBContext {
final int[] elementIndexes;
final int[] elementIndexPrefixes;
final int[] attributeIndexes;
final int[] localNameIndexes;
/**
* The offset of the index
*/
int indexOffset;
/**
* The the maximum known value of an index
*/
int maxIndex;
/**
* True if the tables require clearing
*/
boolean requiresClear;
/**
* Create a new set of tables for a JAXB context.
* <p>
* @param content the JAXB context.
* @param initialIndexOffset the initial index offset to calculate
* the maximum possible index
*
*/
TablesPerJAXBContext(JAXBContextImpl context, int initialIndexOffset) {
elementIndexes = new int[context.getNumberOfElementNames()];
elementIndexPrefixes = new int[context.getNumberOfElementNames()];
attributeIndexes = new int[context.getNumberOfAttributeNames()];
localNameIndexes = new int[context.getNumberOfLocalNames()];
indexOffset = 1;
maxIndex = initialIndexOffset + elementIndexes.length + attributeIndexes.length;
}
/**
* Require that tables are cleared.
*/
public void requireClearTables() {
requiresClear = true;
}
/**
* Clear or reset the tables.
* <p>
* @param initialIndexOffset the initial index offset to calculate
* the maximum possible index
*/
public void clearOrResetTables(int intialIndexOffset) {
if (requiresClear) {
requiresClear = false;
// Increment offset to new position
indexOffset += maxIndex;
// Reset the maximum known value of an index
maxIndex = intialIndexOffset + elementIndexes.length + attributeIndexes.length;
// Check if there is enough free space
// If overflow
if ((indexOffset + maxIndex) < 0) {
clearAll();
}
} else {
// Reset the maximum known value of an index
maxIndex = intialIndexOffset + elementIndexes.length + attributeIndexes.length;
// Check if there is enough free space
// If overflow
if ((indexOffset + maxIndex) < 0) {
resetAll();
}
}
}
private void clearAll() {
clear(elementIndexes);
clear(attributeIndexes);
clear(localNameIndexes);
indexOffset = 1;
}
private void clear(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = 0;
}
}
/**
* Increment the maximum know index value
* <p>
* The indexes are preserved.
*/
public void incrementMaxIndexValue() {
// Increment the maximum value of an index
maxIndex++;
// Check if there is enough free space
// If overflow
if ((indexOffset + maxIndex) < 0) {
resetAll();
}
}
private void resetAll() {
clear(elementIndexes);
clear(attributeIndexes);
clear(localNameIndexes);
indexOffset = 1;
}
private void reset(int[] array) {
for (int i = 0; i < array.length; i++) {
if (array[i] > indexOffset) {
array[i] = array[i] - indexOffset + 1;
} else {
array[i] = 0;
}
}
}
}
/**
* Holder of JAXB contexts -> tables.
* <p>
* An instance will be registered with the
* {@link LowLevelStAXDocumentSerializer}.
*/
final static class AppData implements VocabularyApplicationData {
final Map<JAXBContext, TablesPerJAXBContext> contexts =
new WeakHashMap<JAXBContext, TablesPerJAXBContext>();
final Collection<TablesPerJAXBContext> collectionOfContexts = contexts.values();
/**
* Clear all the tables.
*/
public void clear() {
for(TablesPerJAXBContext c : collectionOfContexts)
c.requireClearTables();
}
}
public FastInfosetStreamWriterOutput(StAXDocumentSerializer out,
JAXBContextImpl context) {
super(out);
this.fiout = out;
this.localNames = context.getUTF8NameTable();
final VocabularyApplicationData vocabAppData = fiout.getVocabularyApplicationData();
AppData appData = null;
if (vocabAppData == null || !(vocabAppData instanceof AppData)) {
appData = new AppData();
fiout.setVocabularyApplicationData(appData);
} else {
appData = (AppData)vocabAppData;
}
final TablesPerJAXBContext tablesPerContext = appData.contexts.get(context);
if (tablesPerContext != null) {
tables = tablesPerContext;
/**
* Obtain the current local name index. Thus will be used to
* calculate the maximum index value when serializing for this context
*/
tables.clearOrResetTables(out.getLocalNameIndex());
} else {
tables = new TablesPerJAXBContext(context, out.getLocalNameIndex());
appData.contexts.put(context, tables);
}
}
@Override
public void startDocument(XMLSerializer serializer, boolean fragment,
int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext)
throws IOException, SAXException, XMLStreamException {
super.startDocument(serializer, fragment, nsUriIndex2prefixIndex, nsContext);
if (fragment)
fiout.initiateLowLevelWriting();
}
@Override
public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException {
super.endDocument(fragment);
}
@Override
public void beginStartTag(Name name) throws IOException {
fiout.writeLowLevelTerminationAndMark();
if (nsContext.getCurrent().count() == 0) {
final int qNameIndex = tables.elementIndexes[name.qNameIndex] - tables.indexOffset;
final int prefixIndex = nsUriIndex2prefixIndex[name.nsUriIndex];
if (qNameIndex >= 0 &&
tables.elementIndexPrefixes[name.qNameIndex] == prefixIndex) {
fiout.writeLowLevelStartElementIndexed(EncodingConstants.ELEMENT, qNameIndex);
} else {
tables.elementIndexes[name.qNameIndex] = fiout.getNextElementIndex() + tables.indexOffset;
tables.elementIndexPrefixes[name.qNameIndex] = prefixIndex;
writeLiteral(EncodingConstants.ELEMENT | EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG,
name,
nsContext.getPrefix(prefixIndex),
nsContext.getNamespaceURI(prefixIndex));
}
} else {
beginStartTagWithNamespaces(name);
}
}
public void beginStartTagWithNamespaces(Name name) throws IOException {
final NamespaceContextImpl.Element nse = nsContext.getCurrent();
fiout.writeLowLevelStartNamespaces();
for (int i = nse.count() - 1; i >= 0; i--) {
final String uri = nse.getNsUri(i);
if (uri.length() == 0 && nse.getBase() == 1)
continue; // no point in definint xmlns='' on the root
fiout.writeLowLevelNamespace(nse.getPrefix(i), uri);
}
fiout.writeLowLevelEndNamespaces();
final int qNameIndex = tables.elementIndexes[name.qNameIndex] - tables.indexOffset;
final int prefixIndex = nsUriIndex2prefixIndex[name.nsUriIndex];
if (qNameIndex >= 0 &&
tables.elementIndexPrefixes[name.qNameIndex] == prefixIndex) {
fiout.writeLowLevelStartElementIndexed(0, qNameIndex);
} else {
tables.elementIndexes[name.qNameIndex] = fiout.getNextElementIndex() + tables.indexOffset;
tables.elementIndexPrefixes[name.qNameIndex] = prefixIndex;
writeLiteral(EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG,
name,
nsContext.getPrefix(prefixIndex),
nsContext.getNamespaceURI(prefixIndex));
}
}
@Override
public void attribute(Name name, String value) throws IOException {
fiout.writeLowLevelStartAttributes();
final int qNameIndex = tables.attributeIndexes[name.qNameIndex] - tables.indexOffset;
if (qNameIndex >= 0) {
fiout.writeLowLevelAttributeIndexed(qNameIndex);
} else {
tables.attributeIndexes[name.qNameIndex] = fiout.getNextAttributeIndex() + tables.indexOffset;
final int namespaceURIId = name.nsUriIndex;
if (namespaceURIId == -1) {
writeLiteral(EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG,
name,
"",
"");
} else {
final int prefix = nsUriIndex2prefixIndex[namespaceURIId];
writeLiteral(EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG,
name,
nsContext.getPrefix(prefix),
nsContext.getNamespaceURI(prefix));
}
}
fiout.writeLowLevelAttributeValue(value);
}
private void writeLiteral(int type, Name name, String prefix, String namespaceURI) throws IOException {
final int localNameIndex = tables.localNameIndexes[name.localNameIndex] - tables.indexOffset;
if (localNameIndex < 0) {
tables.localNameIndexes[name.localNameIndex] = fiout.getNextLocalNameIndex() + tables.indexOffset;
fiout.writeLowLevelStartNameLiteral(
type,
prefix,
localNames[name.localNameIndex].buf,
namespaceURI);
} else {
fiout.writeLowLevelStartNameLiteral(
type,
prefix,
localNameIndex,
namespaceURI);
}
}
@Override
public void endStartTag() throws IOException {
fiout.writeLowLevelEndStartElement();
}
@Override
public void endTag(Name name) throws IOException {
fiout.writeLowLevelEndElement();
}
@Override
public void endTag(int prefix, String localName) throws IOException {
fiout.writeLowLevelEndElement();
}
@Override
public void text(Pcdata value, boolean needsSeparatingWhitespace) throws IOException {
if (needsSeparatingWhitespace)
fiout.writeLowLevelText(" ");
/*
* Check if the CharSequence is from a base64Binary data type
*/
if (!(value instanceof Base64Data)) {
final int len = value.length();
if(len <buf.length) {
value.writeTo(buf, 0);
fiout.writeLowLevelText(buf, len);
} else {
fiout.writeLowLevelText(value.toString());
}
} else {
final Base64Data dataValue = (Base64Data)value;
// Write out the octets using the base64 encoding algorithm
fiout.writeLowLevelOctets(dataValue.get(), dataValue.getDataLen());
}
}
@Override
public void text(String value, boolean needsSeparatingWhitespace) throws IOException {
if (needsSeparatingWhitespace)
fiout.writeLowLevelText(" ");
fiout.writeLowLevelText(value);
}
@Override
public void beginStartTag(int prefix, String localName) throws IOException {
fiout.writeLowLevelTerminationAndMark();
int type = EncodingConstants.ELEMENT;
if (nsContext.getCurrent().count() > 0) {
final NamespaceContextImpl.Element nse = nsContext.getCurrent();
fiout.writeLowLevelStartNamespaces();
for (int i = nse.count() - 1; i >= 0; i--) {
final String uri = nse.getNsUri(i);
if (uri.length() == 0 && nse.getBase() == 1)
continue; // no point in definint xmlns='' on the root
fiout.writeLowLevelNamespace(nse.getPrefix(i), uri);
}
fiout.writeLowLevelEndNamespaces();
type= 0;
}
final boolean isIndexed = fiout.writeLowLevelStartElement(
type,
nsContext.getPrefix(prefix),
localName,
nsContext.getNamespaceURI(prefix));
if (!isIndexed)
tables.incrementMaxIndexValue();
}
@Override
public void attribute(int prefix, String localName, String value) throws IOException {
fiout.writeLowLevelStartAttributes();
boolean isIndexed;
if (prefix == -1)
isIndexed = fiout.writeLowLevelAttribute("", "", localName);
else
isIndexed = fiout.writeLowLevelAttribute(
nsContext.getPrefix(prefix),
nsContext.getNamespaceURI(prefix),
localName);
if (!isIndexed)
tables.incrementMaxIndexValue();
fiout.writeLowLevelAttributeValue(value);
}
}