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