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 com.sun.xml.bind.v2.runtime.JAXBContextImpl;
040: import com.sun.xml.bind.v2.runtime.Name;
041: import com.sun.xml.bind.v2.runtime.XMLSerializer;
042: import javax.xml.stream.XMLStreamException;
043:
044: import com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data;
045: import com.sun.xml.fastinfoset.EncodingConstants;
046: import com.sun.xml.fastinfoset.stax.StAXDocumentSerializer;
047: import java.io.IOException;
048: import java.util.Collection;
049: import java.util.Map;
050: import java.util.WeakHashMap;
051: import javax.xml.bind.JAXBContext;
052: import org.jvnet.fastinfoset.VocabularyApplicationData;
053: import org.xml.sax.SAXException;
054:
055: /**
056: * {@link XmlOutput} for {@link LowLevelStAXDocumentSerializer}.
057: * <p>
058: * This class is responsible for managing the indexing of elements, attributes
059: * and local names that are known to JAXB by way of the JAXBContext (generated
060: * from JAXB beans or schema). The pre-encoded UTF-8 representations of known
061: * local names are also utilized.
062: * <p>
063: * The lookup of elements, attributes and local names with respect to a context
064: * is very efficient. It relies on an incrementing base line so that look up is
065: * performed in O(1) time and only uses static memory. When the base line reaches
066: * a point where integer overflow will occur the arrays and base line are reset
067: * (such an event is rare and will have little impact on performance).
068: * <p>
069: * A weak map of JAXB contexts to optimized tables for attributes, elements and
070: * local names is utilized and stored on the LowLevel StAX serializer. Thus,
071: * optimized serializing can work other multiple serializing of JAXB beans using
072: * the same LowLevel StAX serializer instance. This approach works best when JAXB
073: * contexts are only created once per schema or JAXB beans (which is the recommended
074: * practice as the creation JAXB contexts are expensive, they are thread safe and
075: * can be reused).
076: *
077: * @author Paul.Sandoz@Sun.Com
078: */
079: public final class FastInfosetStreamWriterOutput extends
080: XMLStreamWriterOutput {
081: private final StAXDocumentSerializer fiout;
082: private final Encoded[] localNames;
083: private final TablesPerJAXBContext tables;
084:
085: /**
086: * Holder for the optimzed element, attribute and
087: * local name tables.
088: */
089: final static class TablesPerJAXBContext {
090: final int[] elementIndexes;
091: final int[] attributeIndexes;
092: final int[] localNameIndexes;
093:
094: /**
095: * The offset of the index
096: */
097: int indexOffset;
098:
099: /**
100: * The the maximum known value of an index
101: */
102: int maxIndex;
103:
104: /**
105: * True if the tables require clearing
106: */
107: boolean requiresClear;
108:
109: /**
110: * Create a new set of tables for a JAXB context.
111: * <p>
112: * @param content the JAXB context.
113: * @param initialIndexOffset the initial index offset to calculate
114: * the maximum possible index
115: *
116: */
117: TablesPerJAXBContext(JAXBContextImpl context,
118: int initialIndexOffset) {
119: elementIndexes = new int[context.getNumberOfElementNames()];
120: attributeIndexes = new int[context
121: .getNumberOfAttributeNames()];
122: localNameIndexes = new int[context.getNumberOfLocalNames()];
123:
124: indexOffset = 1;
125: maxIndex = initialIndexOffset + elementIndexes.length
126: + attributeIndexes.length;
127: }
128:
129: /**
130: * Require that tables are cleared.
131: */
132: public void requireClearTables() {
133: requiresClear = true;
134: }
135:
136: /**
137: * Clear or reset the tables.
138: * <p>
139: * @param initialIndexOffset the initial index offset to calculate
140: * the maximum possible index
141: */
142: public void clearOrResetTables(int intialIndexOffset) {
143: if (requiresClear) {
144: requiresClear = false;
145:
146: // Increment offset to new position
147: indexOffset += maxIndex;
148: // Reset the maximum known value of an index
149: maxIndex = intialIndexOffset + elementIndexes.length
150: + attributeIndexes.length;
151: // Check if there is enough free space
152: // If overflow
153: if ((indexOffset + maxIndex) < 0) {
154: clearAll();
155: }
156: } else {
157: // Reset the maximum known value of an index
158: maxIndex = intialIndexOffset + elementIndexes.length
159: + attributeIndexes.length;
160: // Check if there is enough free space
161: // If overflow
162: if ((indexOffset + maxIndex) < 0) {
163: resetAll();
164: }
165: }
166: }
167:
168: private void clearAll() {
169: clear(elementIndexes);
170: clear(attributeIndexes);
171: clear(localNameIndexes);
172: indexOffset = 1;
173: }
174:
175: private void clear(int[] array) {
176: for (int i = 0; i < array.length; i++) {
177: array[i] = 0;
178: }
179: }
180:
181: /**
182: * Increment the maximum know index value
183: * <p>
184: * The indexes are preserved.
185: */
186: public void incrementMaxIndexValue() {
187: // Increment the maximum value of an index
188: maxIndex++;
189: // Check if there is enough free space
190: // If overflow
191: if ((indexOffset + maxIndex) < 0) {
192: resetAll();
193: }
194: }
195:
196: private void resetAll() {
197: clear(elementIndexes);
198: clear(attributeIndexes);
199: clear(localNameIndexes);
200: indexOffset = 1;
201: }
202:
203: private void reset(int[] array) {
204: for (int i = 0; i < array.length; i++) {
205: if (array[i] > indexOffset) {
206: array[i] = array[i] - indexOffset + 1;
207: } else {
208: array[i] = 0;
209: }
210: }
211: }
212:
213: }
214:
215: /**
216: * Holder of JAXB contexts -> tables.
217: * <p>
218: * An instance will be registered with the
219: * {@link LowLevelStAXDocumentSerializer}.
220: */
221: final static class AppData implements VocabularyApplicationData {
222: final Map<JAXBContext, TablesPerJAXBContext> contexts = new WeakHashMap<JAXBContext, TablesPerJAXBContext>();
223: final Collection<TablesPerJAXBContext> collectionOfContexts = contexts
224: .values();
225:
226: /**
227: * Clear all the tables.
228: */
229: public void clear() {
230: for (TablesPerJAXBContext c : collectionOfContexts)
231: c.requireClearTables();
232: }
233: }
234:
235: public FastInfosetStreamWriterOutput(StAXDocumentSerializer out,
236: JAXBContextImpl context) {
237: super (out);
238:
239: this .fiout = out;
240: this .localNames = context.getUTF8NameTable();
241:
242: final VocabularyApplicationData vocabAppData = fiout
243: .getVocabularyApplicationData();
244: AppData appData = null;
245: if (vocabAppData == null || !(vocabAppData instanceof AppData)) {
246: appData = new AppData();
247: fiout.setVocabularyApplicationData(appData);
248: } else {
249: appData = (AppData) vocabAppData;
250: }
251:
252: final TablesPerJAXBContext tablesPerContext = appData.contexts
253: .get(context);
254: if (tablesPerContext != null) {
255: tables = tablesPerContext;
256: /**
257: * Obtain the current local name index. Thus will be used to
258: * calculate the maximum index value when serializing for this context
259: */
260: tables.clearOrResetTables(out.getLocalNameIndex());
261: } else {
262: tables = new TablesPerJAXBContext(context, out
263: .getLocalNameIndex());
264: appData.contexts.put(context, tables);
265: }
266: }
267:
268: public void startDocument(XMLSerializer serializer,
269: boolean fragment, int[] nsUriIndex2prefixIndex,
270: NamespaceContextImpl nsContext) throws IOException,
271: SAXException, XMLStreamException {
272: super .startDocument(serializer, fragment,
273: nsUriIndex2prefixIndex, nsContext);
274:
275: if (fragment)
276: fiout.initiateLowLevelWriting();
277: }
278:
279: public void endDocument(boolean fragment) throws IOException,
280: SAXException, XMLStreamException {
281: super .endDocument(fragment);
282: }
283:
284: public void beginStartTag(Name name) throws IOException {
285: fiout.writeLowLevelTerminationAndMark();
286:
287: if (nsContext.getCurrent().count() == 0) {
288: final int qNameIndex = tables.elementIndexes[name.qNameIndex]
289: - tables.indexOffset;
290: if (qNameIndex >= 0) {
291: fiout.writeLowLevelStartElementIndexed(
292: EncodingConstants.ELEMENT, qNameIndex);
293: } else {
294: tables.elementIndexes[name.qNameIndex] = fiout
295: .getNextElementIndex()
296: + tables.indexOffset;
297:
298: final int prefix = nsUriIndex2prefixIndex[name.nsUriIndex];
299: writeLiteral(EncodingConstants.ELEMENT
300: | EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG,
301: name, nsContext.getPrefix(prefix), nsContext
302: .getNamespaceURI(prefix));
303: }
304: } else {
305: beginStartTagWithNamespaces(name);
306: }
307: }
308:
309: public void beginStartTagWithNamespaces(Name name)
310: throws IOException {
311: final NamespaceContextImpl.Element nse = nsContext.getCurrent();
312:
313: fiout.writeLowLevelStartNamespaces();
314: for (int i = nse.count() - 1; i >= 0; i--) {
315: final String uri = nse.getNsUri(i);
316: if (uri.length() == 0 && nse.getBase() == 1)
317: continue; // no point in definint xmlns='' on the root
318: fiout.writeLowLevelNamespace(nse.getPrefix(i), uri);
319: }
320: fiout.writeLowLevelEndNamespaces();
321:
322: final int qNameIndex = tables.elementIndexes[name.qNameIndex]
323: - tables.indexOffset;
324: if (qNameIndex >= 0) {
325: fiout.writeLowLevelStartElementIndexed(0, qNameIndex);
326: } else {
327: tables.elementIndexes[name.qNameIndex] = fiout
328: .getNextElementIndex()
329: + tables.indexOffset;
330:
331: final int prefix = nsUriIndex2prefixIndex[name.nsUriIndex];
332: writeLiteral(EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG,
333: name, nsContext.getPrefix(prefix), nsContext
334: .getNamespaceURI(prefix));
335: }
336: }
337:
338: public void attribute(Name name, String value) throws IOException {
339: fiout.writeLowLevelStartAttributes();
340:
341: final int qNameIndex = tables.attributeIndexes[name.qNameIndex]
342: - tables.indexOffset;
343: if (qNameIndex >= 0) {
344: fiout.writeLowLevelAttributeIndexed(qNameIndex);
345: } else {
346: tables.attributeIndexes[name.qNameIndex] = fiout
347: .getNextAttributeIndex()
348: + tables.indexOffset;
349:
350: final int namespaceURIId = name.nsUriIndex;
351: if (namespaceURIId == -1) {
352: writeLiteral(
353: EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG,
354: name, "", "");
355: } else {
356: final int prefix = nsUriIndex2prefixIndex[namespaceURIId];
357: writeLiteral(
358: EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG,
359: name, nsContext.getPrefix(prefix), nsContext
360: .getNamespaceURI(prefix));
361: }
362: }
363:
364: fiout.writeLowLevelAttributeValue(value);
365: }
366:
367: private void writeLiteral(int type, Name name, String prefix,
368: String namespaceURI) throws IOException {
369: final int localNameIndex = tables.localNameIndexes[name.localNameIndex]
370: - tables.indexOffset;
371:
372: if (localNameIndex < 0) {
373: tables.localNameIndexes[name.localNameIndex] = fiout
374: .getNextLocalNameIndex()
375: + tables.indexOffset;
376:
377: fiout.writeLowLevelStartNameLiteral(type, prefix,
378: localNames[name.localNameIndex].buf, namespaceURI);
379: } else {
380: fiout.writeLowLevelStartNameLiteral(type, prefix,
381: localNameIndex, namespaceURI);
382: }
383: }
384:
385: public void endStartTag() throws IOException {
386: fiout.writeLowLevelEndStartElement();
387: }
388:
389: public void endTag(Name name) throws IOException {
390: fiout.writeLowLevelEndElement();
391: }
392:
393: public void endTag(int prefix, String localName) throws IOException {
394: fiout.writeLowLevelEndElement();
395: }
396:
397: public void text(Pcdata value, boolean needsSeparatingWhitespace)
398: throws IOException {
399: if (needsSeparatingWhitespace)
400: fiout.writeLowLevelText(" ");
401:
402: /*
403: * Check if the CharSequence is from a base64Binary data type
404: */
405: if (!(value instanceof Base64Data)) {
406: final int len = value.length();
407: if (len < buf.length) {
408: value.writeTo(buf, 0);
409: fiout.writeLowLevelText(buf, len);
410: } else {
411: fiout.writeLowLevelText(value.toString());
412: }
413: } else {
414: final Base64Data dataValue = (Base64Data) value;
415: // Write out the octets using the base64 encoding algorithm
416: fiout.writeLowLevelOctets(dataValue.get(), dataValue
417: .getDataLen());
418: }
419: }
420:
421: public void text(String value, boolean needsSeparatingWhitespace)
422: throws IOException {
423: if (needsSeparatingWhitespace)
424: fiout.writeLowLevelText(" ");
425:
426: fiout.writeLowLevelText(value);
427: }
428:
429: public void beginStartTag(int prefix, String localName)
430: throws IOException {
431: fiout.writeLowLevelTerminationAndMark();
432:
433: int type = EncodingConstants.ELEMENT;
434: if (nsContext.getCurrent().count() > 0) {
435: final NamespaceContextImpl.Element nse = nsContext
436: .getCurrent();
437:
438: fiout.writeLowLevelStartNamespaces();
439: for (int i = nse.count() - 1; i >= 0; i--) {
440: final String uri = nse.getNsUri(i);
441: if (uri.length() == 0 && nse.getBase() == 1)
442: continue; // no point in definint xmlns='' on the root
443: fiout.writeLowLevelNamespace(nse.getPrefix(i), uri);
444: }
445: fiout.writeLowLevelEndNamespaces();
446:
447: type = 0;
448: }
449:
450: final boolean isIndexed = fiout.writeLowLevelStartElement(type,
451: nsContext.getPrefix(prefix), localName, nsContext
452: .getNamespaceURI(prefix));
453:
454: if (!isIndexed)
455: tables.incrementMaxIndexValue();
456: }
457:
458: public void attribute(int prefix, String localName, String value)
459: throws IOException {
460: fiout.writeLowLevelStartAttributes();
461:
462: boolean isIndexed;
463: if (prefix == -1)
464: isIndexed = fiout.writeLowLevelAttribute("", "", localName);
465: else
466: isIndexed = fiout.writeLowLevelAttribute(nsContext
467: .getPrefix(prefix), nsContext
468: .getNamespaceURI(prefix), localName);
469:
470: if (!isIndexed)
471: tables.incrementMaxIndexValue();
472:
473: fiout.writeLowLevelAttributeValue(value);
474: }
475: }
|