1 /* 2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.bind.v2.runtime.output; 27 28 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; 29 import com.sun.xml.internal.bind.v2.runtime.Name; 30 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; 31 import javax.xml.stream.XMLStreamException; 32 33 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data; 34 import com.sun.xml.internal.fastinfoset.EncodingConstants; 35 import com.sun.xml.internal.fastinfoset.stax.StAXDocumentSerializer; 36 import java.io.IOException; 37 import java.util.Collection; 38 import java.util.Map; 39 import java.util.WeakHashMap; 40 import javax.xml.bind.JAXBContext; 41 import com.sun.xml.internal.org.jvnet.fastinfoset.VocabularyApplicationData; 42 import org.xml.sax.SAXException; 43 44 /** 45 * {@link XmlOutput} for {@link LowLevelStAXDocumentSerializer}. 46 * <p> 47 * This class is responsible for managing the indexing of elements, attributes 48 * and local names that are known to JAXB by way of the JAXBContext (generated 49 * from JAXB beans or schema). The pre-encoded UTF-8 representations of known 50 * local names are also utilized. 51 * <p> 52 * The lookup of elements, attributes and local names with respect to a context 53 * is very efficient. It relies on an incrementing base line so that look up is 54 * performed in O(1) time and only uses static memory. When the base line reaches 55 * a point where integer overflow will occur the arrays and base line are reset 56 * (such an event is rare and will have little impact on performance). 57 * <p> 58 * A weak map of JAXB contexts to optimized tables for attributes, elements and 59 * local names is utilized and stored on the LowLevel StAX serializer. Thus, 60 * optimized serializing can work other multiple serializing of JAXB beans using 61 * the same LowLevel StAX serializer instance. This approach works best when JAXB 62 * contexts are only created once per schema or JAXB beans (which is the recommended 63 * practice as the creation JAXB contexts are expensive, they are thread safe and 64 * can be reused). 65 * 66 * @author Paul.Sandoz@Sun.Com 67 */ 68 public final class FastInfosetStreamWriterOutput extends XMLStreamWriterOutput { 69 private final StAXDocumentSerializer fiout; 70 private final Encoded[] localNames; 71 private final TablesPerJAXBContext tables; 72 73 /** 74 * Holder for the optimzed element, attribute and 75 * local name tables. 76 */ 77 final static class TablesPerJAXBContext { 78 final int[] elementIndexes; 79 final int[] elementIndexPrefixes; 80 81 final int[] attributeIndexes; 82 final int[] localNameIndexes; 83 84 /** 85 * The offset of the index 86 */ 87 int indexOffset; 88 89 /** 90 * The the maximum known value of an index 91 */ 92 int maxIndex; 93 94 /** 95 * True if the tables require clearing 96 */ 97 boolean requiresClear; 98 99 /** 100 * Create a new set of tables for a JAXB context. 101 * <p> 102 * @param content the JAXB context. 103 * @param initialIndexOffset the initial index offset to calculate 104 * the maximum possible index 105 * 106 */ 107 TablesPerJAXBContext(JAXBContextImpl context, int initialIndexOffset) { 108 elementIndexes = new int[context.getNumberOfElementNames()]; 109 elementIndexPrefixes = new int[context.getNumberOfElementNames()]; 110 attributeIndexes = new int[context.getNumberOfAttributeNames()]; 111 localNameIndexes = new int[context.getNumberOfLocalNames()]; 112 113 indexOffset = 1; 114 maxIndex = initialIndexOffset + elementIndexes.length + attributeIndexes.length; 115 } 116 117 /** 118 * Require that tables are cleared. 119 */ 120 public void requireClearTables() { 121 requiresClear = true; 122 } 123 124 /** 125 * Clear or reset the tables. 126 * <p> 127 * @param initialIndexOffset the initial index offset to calculate 128 * the maximum possible index 129 */ 130 public void clearOrResetTables(int intialIndexOffset) { 131 if (requiresClear) { 132 requiresClear = false; 133 134 // Increment offset to new position 135 indexOffset += maxIndex; 136 // Reset the maximum known value of an index 137 maxIndex = intialIndexOffset + elementIndexes.length + attributeIndexes.length; 138 // Check if there is enough free space 139 // If overflow 140 if ((indexOffset + maxIndex) < 0) { 141 clearAll(); 142 } 143 } else { 144 // Reset the maximum known value of an index 145 maxIndex = intialIndexOffset + elementIndexes.length + attributeIndexes.length; 146 // Check if there is enough free space 147 // If overflow 148 if ((indexOffset + maxIndex) < 0) { 149 resetAll(); 150 } 151 } 152 } 153 154 private void clearAll() { 155 clear(elementIndexes); 156 clear(attributeIndexes); 157 clear(localNameIndexes); 158 indexOffset = 1; 159 } 160 161 private void clear(int[] array) { 162 for (int i = 0; i < array.length; i++) { 163 array[i] = 0; 164 } 165 } 166 167 /** 168 * Increment the maximum know index value 169 * <p> 170 * The indexes are preserved. 171 */ 172 public void incrementMaxIndexValue() { 173 // Increment the maximum value of an index 174 maxIndex++; 175 // Check if there is enough free space 176 // If overflow 177 if ((indexOffset + maxIndex) < 0) { 178 resetAll(); 179 } 180 } 181 182 private void resetAll() { 183 clear(elementIndexes); 184 clear(attributeIndexes); 185 clear(localNameIndexes); 186 indexOffset = 1; 187 } 188 189 private void reset(int[] array) { 190 for (int i = 0; i < array.length; i++) { 191 if (array[i] > indexOffset) { 192 array[i] = array[i] - indexOffset + 1; 193 } else { 194 array[i] = 0; 195 } 196 } 197 } 198 199 } 200 201 /** 202 * Holder of JAXB contexts -> tables. 203 * <p> 204 * An instance will be registered with the 205 * {@link LowLevelStAXDocumentSerializer}. 206 */ 207 final static class AppData implements VocabularyApplicationData { 208 final Map<JAXBContext, TablesPerJAXBContext> contexts = 209 new WeakHashMap<JAXBContext, TablesPerJAXBContext>(); 210 final Collection<TablesPerJAXBContext> collectionOfContexts = contexts.values(); 211 212 /** 213 * Clear all the tables. 214 */ 215 public void clear() { 216 for(TablesPerJAXBContext c : collectionOfContexts) 217 c.requireClearTables(); 218 } 219 } 220 221 public FastInfosetStreamWriterOutput(StAXDocumentSerializer out, 222 JAXBContextImpl context) { 223 super(out); 224 225 this.fiout = out; 226 this.localNames = context.getUTF8NameTable(); 227 228 final VocabularyApplicationData vocabAppData = fiout.getVocabularyApplicationData(); 229 AppData appData = null; 230 if (vocabAppData == null || !(vocabAppData instanceof AppData)) { 231 appData = new AppData(); 232 fiout.setVocabularyApplicationData(appData); 233 } else { 234 appData = (AppData)vocabAppData; 235 } 236 237 final TablesPerJAXBContext tablesPerContext = appData.contexts.get(context); 238 if (tablesPerContext != null) { 239 tables = tablesPerContext; 240 /** 241 * Obtain the current local name index. Thus will be used to 242 * calculate the maximum index value when serializing for this context 243 */ 244 tables.clearOrResetTables(out.getLocalNameIndex()); 245 } else { 246 tables = new TablesPerJAXBContext(context, out.getLocalNameIndex()); 247 appData.contexts.put(context, tables); 248 } 249 } 250 251 @Override 252 public void startDocument(XMLSerializer serializer, boolean fragment, 253 int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) 254 throws IOException, SAXException, XMLStreamException { 255 super.startDocument(serializer, fragment, nsUriIndex2prefixIndex, nsContext); 256 257 if (fragment) 258 fiout.initiateLowLevelWriting(); 259 } 260 261 @Override 262 public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException { 263 super.endDocument(fragment); 264 } 265 266 @Override 267 public void beginStartTag(Name name) throws IOException { 268 fiout.writeLowLevelTerminationAndMark(); 269 270 if (nsContext.getCurrent().count() == 0) { 271 final int qNameIndex = tables.elementIndexes[name.qNameIndex] - tables.indexOffset; 272 final int prefixIndex = nsUriIndex2prefixIndex[name.nsUriIndex]; 273 274 if (qNameIndex >= 0 && 275 tables.elementIndexPrefixes[name.qNameIndex] == prefixIndex) { 276 fiout.writeLowLevelStartElementIndexed(EncodingConstants.ELEMENT, qNameIndex); 277 } else { 278 tables.elementIndexes[name.qNameIndex] = fiout.getNextElementIndex() + tables.indexOffset; 279 tables.elementIndexPrefixes[name.qNameIndex] = prefixIndex; 280 writeLiteral(EncodingConstants.ELEMENT | EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG, 281 name, 282 nsContext.getPrefix(prefixIndex), 283 nsContext.getNamespaceURI(prefixIndex)); 284 } 285 } else { 286 beginStartTagWithNamespaces(name); 287 } 288 } 289 290 public void beginStartTagWithNamespaces(Name name) throws IOException { 291 final NamespaceContextImpl.Element nse = nsContext.getCurrent(); 292 293 fiout.writeLowLevelStartNamespaces(); 294 for (int i = nse.count() - 1; i >= 0; i--) { 295 final String uri = nse.getNsUri(i); 296 if (uri.length() == 0 && nse.getBase() == 1) 297 continue; // no point in definint xmlns='' on the root 298 fiout.writeLowLevelNamespace(nse.getPrefix(i), uri); 299 } 300 fiout.writeLowLevelEndNamespaces(); 301 302 final int qNameIndex = tables.elementIndexes[name.qNameIndex] - tables.indexOffset; 303 final int prefixIndex = nsUriIndex2prefixIndex[name.nsUriIndex]; 304 305 if (qNameIndex >= 0 && 306 tables.elementIndexPrefixes[name.qNameIndex] == prefixIndex) { 307 fiout.writeLowLevelStartElementIndexed(0, qNameIndex); 308 } else { 309 tables.elementIndexes[name.qNameIndex] = fiout.getNextElementIndex() + tables.indexOffset; 310 tables.elementIndexPrefixes[name.qNameIndex] = prefixIndex; 311 writeLiteral(EncodingConstants.ELEMENT_LITERAL_QNAME_FLAG, 312 name, 313 nsContext.getPrefix(prefixIndex), 314 nsContext.getNamespaceURI(prefixIndex)); 315 } 316 } 317 318 @Override 319 public void attribute(Name name, String value) throws IOException { 320 fiout.writeLowLevelStartAttributes(); 321 322 final int qNameIndex = tables.attributeIndexes[name.qNameIndex] - tables.indexOffset; 323 if (qNameIndex >= 0) { 324 fiout.writeLowLevelAttributeIndexed(qNameIndex); 325 } else { 326 tables.attributeIndexes[name.qNameIndex] = fiout.getNextAttributeIndex() + tables.indexOffset; 327 328 final int namespaceURIId = name.nsUriIndex; 329 if (namespaceURIId == -1) { 330 writeLiteral(EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG, 331 name, 332 "", 333 ""); 334 } else { 335 final int prefix = nsUriIndex2prefixIndex[namespaceURIId]; 336 writeLiteral(EncodingConstants.ATTRIBUTE_LITERAL_QNAME_FLAG, 337 name, 338 nsContext.getPrefix(prefix), 339 nsContext.getNamespaceURI(prefix)); 340 } 341 } 342 343 fiout.writeLowLevelAttributeValue(value); 344 } 345 346 private void writeLiteral(int type, Name name, String prefix, String namespaceURI) throws IOException { 347 final int localNameIndex = tables.localNameIndexes[name.localNameIndex] - tables.indexOffset; 348 349 if (localNameIndex < 0) { 350 tables.localNameIndexes[name.localNameIndex] = fiout.getNextLocalNameIndex() + tables.indexOffset; 351 352 fiout.writeLowLevelStartNameLiteral( 353 type, 354 prefix, 355 localNames[name.localNameIndex].buf, 356 namespaceURI); 357 } else { 358 fiout.writeLowLevelStartNameLiteral( 359 type, 360 prefix, 361 localNameIndex, 362 namespaceURI); 363 } 364 } 365 366 @Override 367 public void endStartTag() throws IOException { 368 fiout.writeLowLevelEndStartElement(); 369 } 370 371 @Override 372 public void endTag(Name name) throws IOException { 373 fiout.writeLowLevelEndElement(); 374 } 375 376 @Override 377 public void endTag(int prefix, String localName) throws IOException { 378 fiout.writeLowLevelEndElement(); 379 } 380 381 382 @Override 383 public void text(Pcdata value, boolean needsSeparatingWhitespace) throws IOException { 384 if (needsSeparatingWhitespace) 385 fiout.writeLowLevelText(" "); 386 387 /* 388 * Check if the CharSequence is from a base64Binary data type 389 */ 390 if (!(value instanceof Base64Data)) { 391 final int len = value.length(); 392 if(len <buf.length) { 393 value.writeTo(buf, 0); 394 fiout.writeLowLevelText(buf, len); 395 } else { 396 fiout.writeLowLevelText(value.toString()); 397 } 398 } else { 399 final Base64Data dataValue = (Base64Data)value; 400 // Write out the octets using the base64 encoding algorithm 401 fiout.writeLowLevelOctets(dataValue.get(), dataValue.getDataLen()); 402 } 403 } 404 405 406 @Override 407 public void text(String value, boolean needsSeparatingWhitespace) throws IOException { 408 if (needsSeparatingWhitespace) 409 fiout.writeLowLevelText(" "); 410 411 fiout.writeLowLevelText(value); 412 } 413 414 415 @Override 416 public void beginStartTag(int prefix, String localName) throws IOException { 417 fiout.writeLowLevelTerminationAndMark(); 418 419 int type = EncodingConstants.ELEMENT; 420 if (nsContext.getCurrent().count() > 0) { 421 final NamespaceContextImpl.Element nse = nsContext.getCurrent(); 422 423 fiout.writeLowLevelStartNamespaces(); 424 for (int i = nse.count() - 1; i >= 0; i--) { 425 final String uri = nse.getNsUri(i); 426 if (uri.length() == 0 && nse.getBase() == 1) 427 continue; // no point in definint xmlns='' on the root 428 fiout.writeLowLevelNamespace(nse.getPrefix(i), uri); 429 } 430 fiout.writeLowLevelEndNamespaces(); 431 432 type= 0; 433 } 434 435 final boolean isIndexed = fiout.writeLowLevelStartElement( 436 type, 437 nsContext.getPrefix(prefix), 438 localName, 439 nsContext.getNamespaceURI(prefix)); 440 441 if (!isIndexed) 442 tables.incrementMaxIndexValue(); 443 } 444 445 @Override 446 public void attribute(int prefix, String localName, String value) throws IOException { 447 fiout.writeLowLevelStartAttributes(); 448 449 boolean isIndexed; 450 if (prefix == -1) 451 isIndexed = fiout.writeLowLevelAttribute("", "", localName); 452 else 453 isIndexed = fiout.writeLowLevelAttribute( 454 nsContext.getPrefix(prefix), 455 nsContext.getNamespaceURI(prefix), 456 localName); 457 458 if (!isIndexed) 459 tables.incrementMaxIndexValue(); 460 461 fiout.writeLowLevelAttributeValue(value); 462 } 463 }