1 /* 2 * Copyright (c) 1997, 2015, 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 java.io.IOException; 29 import java.io.OutputStream; 30 31 import java.io.StringWriter; 32 import javax.xml.stream.XMLStreamException; 33 34 import com.sun.xml.internal.bind.DatatypeConverterImpl; 35 import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler; 36 import com.sun.xml.internal.bind.v2.runtime.Name; 37 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; 38 import com.sun.xml.internal.bind.v2.runtime.MarshallerImpl; 39 40 import org.xml.sax.SAXException; 41 42 /** 43 * {@link XmlOutput} implementation specialized for UTF-8. 44 * 45 * @author Kohsuke Kawaguchi 46 * @author Paul Sandoz 47 */ 48 public class UTF8XmlOutput extends XmlOutputAbstractImpl { 49 protected final OutputStream out; 50 51 /** prefixes encoded. */ 52 private Encoded[] prefixes = new Encoded[8]; 53 54 /** 55 * Of the {@link #prefixes}, number of filled entries. 56 * This is almost the same as {@link NamespaceContextImpl#count()}, 57 * except that it allows us to handle contextual in-scope namespace bindings correctly. 58 */ 59 private int prefixCount; 60 61 /** local names encoded in UTF-8. All entries are pre-filled. */ 62 private final Encoded[] localNames; 63 64 /** Temporary buffer used to encode text. */ 65 /* 66 * TODO 67 * The textBuffer could write directly to the _octetBuffer 68 * when encoding a string if Encoder is modified. 69 * This will avoid an additional memory copy. 70 */ 71 private final Encoded textBuffer = new Encoded(); 72 73 /** Buffer of octets for writing. */ 74 // TODO: Obtain buffer size from property on the JAXB context 75 protected final byte[] octetBuffer = new byte[1024]; 76 77 /** Index in buffer to write to. */ 78 protected int octetBufferIndex; 79 80 /** 81 * Set to true to indicate that we need to write {@code '>'} 82 * to close a start tag. Deferring the write of this char 83 * allows us to write {@code "/>"} for empty elements. 84 */ 85 protected boolean closeStartTagPending = false; 86 87 /** 88 * @see MarshallerImpl#header 89 */ 90 private String header; 91 92 private CharacterEscapeHandler escapeHandler = null; 93 94 /** 95 * 96 * @param localNames 97 * local names encoded in UTF-8. 98 */ 99 public UTF8XmlOutput(OutputStream out, Encoded[] localNames, CharacterEscapeHandler escapeHandler) { 100 this.out = out; 101 this.localNames = localNames; 102 for( int i=0; i<prefixes.length; i++ ) 103 prefixes[i] = new Encoded(); 104 this.escapeHandler = escapeHandler; 105 } 106 107 public void setHeader(String header) { 108 this.header = header; 109 } 110 111 @Override 112 public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws IOException, SAXException, XMLStreamException { 113 super.startDocument(serializer, fragment,nsUriIndex2prefixIndex,nsContext); 114 115 octetBufferIndex = 0; 116 if(!fragment) { 117 write(XML_DECL); 118 } 119 if(header!=null) { 120 textBuffer.set(header); 121 textBuffer.write(this); 122 } 123 } 124 125 @Override 126 public void endDocument(boolean fragment) throws IOException, SAXException, XMLStreamException { 127 flushBuffer(); 128 super.endDocument(fragment); 129 } 130 131 /** 132 * Writes {@code '>'} to close the start tag, if necessary. 133 */ 134 protected final void closeStartTag() throws IOException { 135 if(closeStartTagPending) { 136 write('>'); 137 closeStartTagPending = false; 138 } 139 } 140 141 public void beginStartTag(int prefix, String localName) throws IOException { 142 closeStartTag(); 143 int base= pushNsDecls(); 144 write('<'); 145 writeName(prefix,localName); 146 writeNsDecls(base); 147 } 148 149 @Override 150 public void beginStartTag(Name name) throws IOException { 151 closeStartTag(); 152 int base = pushNsDecls(); 153 write('<'); 154 writeName(name); 155 writeNsDecls(base); 156 } 157 158 private int pushNsDecls() { 159 int total = nsContext.count(); 160 NamespaceContextImpl.Element ns = nsContext.getCurrent(); 161 162 if(total > prefixes.length) { 163 // reallocate 164 int m = Math.max(total,prefixes.length*2); 165 Encoded[] buf = new Encoded[m]; 166 System.arraycopy(prefixes,0,buf,0,prefixes.length); 167 for( int i=prefixes.length; i<buf.length; i++ ) 168 buf[i] = new Encoded(); 169 prefixes = buf; 170 } 171 172 int base = Math.min(prefixCount,ns.getBase()); 173 int size = nsContext.count(); 174 for( int i=base; i<size; i++ ) { 175 String p = nsContext.getPrefix(i); 176 177 Encoded e = prefixes[i]; 178 179 if(p.length()==0) { 180 e.buf = EMPTY_BYTE_ARRAY; 181 e.len = 0; 182 } else { 183 e.set(p); 184 e.append(':'); 185 } 186 } 187 prefixCount = size; 188 return base; 189 } 190 191 protected void writeNsDecls(int base) throws IOException { 192 NamespaceContextImpl.Element ns = nsContext.getCurrent(); 193 int size = nsContext.count(); 194 195 for( int i=ns.getBase(); i<size; i++ ) 196 writeNsDecl(i); 197 } 198 199 /** 200 * Writes a single namespace declaration for the specified prefix. 201 */ 202 protected final void writeNsDecl(int prefixIndex) throws IOException { 203 String p = nsContext.getPrefix(prefixIndex); 204 205 if(p.length()==0) { 206 if(nsContext.getCurrent().isRootElement() 207 && nsContext.getNamespaceURI(prefixIndex).length()==0) 208 return; // no point in declaring xmlns="" on the root element 209 write(XMLNS_EQUALS); 210 } else { 211 Encoded e = prefixes[prefixIndex]; 212 write(XMLNS_COLON); 213 write(e.buf,0,e.len-1); // skip the trailing ':' 214 write(EQUALS); 215 } 216 doText(nsContext.getNamespaceURI(prefixIndex),true); 217 write('\"'); 218 } 219 220 private void writePrefix(int prefix) throws IOException { 221 prefixes[prefix].write(this); 222 } 223 224 private void writeName(Name name) throws IOException { 225 writePrefix(nsUriIndex2prefixIndex[name.nsUriIndex]); 226 localNames[name.localNameIndex].write(this); 227 } 228 229 private void writeName(int prefix, String localName) throws IOException { 230 writePrefix(prefix); 231 textBuffer.set(localName); 232 textBuffer.write(this); 233 } 234 235 @Override 236 public void attribute(Name name, String value) throws IOException { 237 write(' '); 238 if(name.nsUriIndex==-1) { 239 localNames[name.localNameIndex].write(this); 240 } else 241 writeName(name); 242 write(EQUALS); 243 doText(value,true); 244 write('\"'); 245 } 246 247 public void attribute(int prefix, String localName, String value) throws IOException { 248 write(' '); 249 if(prefix==-1) { 250 textBuffer.set(localName); 251 textBuffer.write(this); 252 } else 253 writeName(prefix,localName); 254 write(EQUALS); 255 doText(value,true); 256 write('\"'); 257 } 258 259 public void endStartTag() throws IOException { 260 closeStartTagPending = true; 261 } 262 263 @Override 264 public void endTag(Name name) throws IOException { 265 if(closeStartTagPending) { 266 write(EMPTY_TAG); 267 closeStartTagPending = false; 268 } else { 269 write(CLOSE_TAG); 270 writeName(name); 271 write('>'); 272 } 273 } 274 275 public void endTag(int prefix, String localName) throws IOException { 276 if(closeStartTagPending) { 277 write(EMPTY_TAG); 278 closeStartTagPending = false; 279 } else { 280 write(CLOSE_TAG); 281 writeName(prefix,localName); 282 write('>'); 283 } 284 } 285 286 public void text(String value, boolean needSP) throws IOException { 287 closeStartTag(); 288 if(needSP) 289 write(' '); 290 doText(value,false); 291 } 292 293 public void text(Pcdata value, boolean needSP) throws IOException { 294 closeStartTag(); 295 if(needSP) 296 write(' '); 297 value.writeTo(this); 298 } 299 300 private void doText(String value,boolean isAttribute) throws IOException { 301 if (escapeHandler != null) { 302 StringWriter sw = new StringWriter(); 303 escapeHandler.escape(value.toCharArray(), 0, value.length(), isAttribute, sw); 304 textBuffer.set(sw.toString()); 305 } else { 306 textBuffer.setEscape(value, isAttribute); 307 } 308 textBuffer.write(this); 309 } 310 311 public final void text(int value) throws IOException { 312 closeStartTag(); 313 /* 314 * TODO 315 * Change to use the octet buffer directly 316 */ 317 318 // max is -2147483648 and 11 digits 319 boolean minus = (value<0); 320 textBuffer.ensureSize(11); 321 byte[] buf = textBuffer.buf; 322 int idx = 11; 323 324 do { 325 int r = value%10; 326 if(r<0) r = -r; 327 buf[--idx] = (byte)('0'|r); // really measn 0x30+r but 0<=r<10, so bit-OR would do. 328 value /= 10; 329 } while(value!=0); 330 331 if(minus) buf[--idx] = (byte)'-'; 332 333 write(buf,idx,11-idx); 334 } 335 336 /** 337 * Writes the given byte[] as base64 encoded binary to the output. 338 * 339 * <p> 340 * Being defined on this class allows this method to access the buffer directly, 341 * which translates to a better performance. 342 */ 343 public void text(byte[] data, int dataLen) throws IOException { 344 closeStartTag(); 345 346 int start = 0; 347 348 while(dataLen>0) { 349 // how many bytes (in data) can we write without overflowing the buffer? 350 int batchSize = Math.min(((octetBuffer.length-octetBufferIndex)/4)*3,dataLen); 351 352 // write the batch 353 octetBufferIndex = DatatypeConverterImpl._printBase64Binary(data,start,batchSize,octetBuffer,octetBufferIndex); 354 355 if(batchSize<dataLen) 356 flushBuffer(); 357 358 start += batchSize; 359 dataLen -= batchSize; 360 361 } 362 } 363 364 // 365 // 366 // series of the write method that places bytes to the output 367 // (by doing some buffering internal to this class) 368 // 369 370 /** 371 * Writes one byte directly into the buffer. 372 * 373 * <p> 374 * This method can be used somewhat like the {@code text} method, 375 * but it doesn't perform character escaping. 376 */ 377 public final void write(int i) throws IOException { 378 if (octetBufferIndex < octetBuffer.length) { 379 octetBuffer[octetBufferIndex++] = (byte)i; 380 } else { 381 out.write(octetBuffer); 382 octetBufferIndex = 1; 383 octetBuffer[0] = (byte)i; 384 } 385 } 386 387 protected final void write(byte[] b) throws IOException { 388 write(b, 0, b.length); 389 } 390 391 protected final void write(byte[] b, int start, int length) throws IOException { 392 if ((octetBufferIndex + length) < octetBuffer.length) { 393 System.arraycopy(b, start, octetBuffer, octetBufferIndex, length); 394 octetBufferIndex += length; 395 } else { 396 out.write(octetBuffer, 0, octetBufferIndex); 397 out.write(b, start, length); 398 octetBufferIndex = 0; 399 } 400 } 401 402 protected final void flushBuffer() throws IOException { 403 out.write(octetBuffer, 0, octetBufferIndex); 404 octetBufferIndex = 0; 405 } 406 407 static byte[] toBytes(String s) { 408 byte[] buf = new byte[s.length()]; 409 for( int i=s.length()-1; i>=0; i-- ) 410 buf[i] = (byte)s.charAt(i); 411 return buf; 412 } 413 414 // per instance copy to prevent an attack where malicious OutputStream 415 // rewrites the byte array. 416 private final byte[] XMLNS_EQUALS = _XMLNS_EQUALS.clone(); 417 private final byte[] XMLNS_COLON = _XMLNS_COLON.clone(); 418 private final byte[] EQUALS = _EQUALS.clone(); 419 private final byte[] CLOSE_TAG = _CLOSE_TAG.clone(); 420 private final byte[] EMPTY_TAG = _EMPTY_TAG.clone(); 421 private final byte[] XML_DECL = _XML_DECL.clone(); 422 423 // masters 424 private static final byte[] _XMLNS_EQUALS = toBytes(" xmlns=\""); 425 private static final byte[] _XMLNS_COLON = toBytes(" xmlns:"); 426 private static final byte[] _EQUALS = toBytes("=\""); 427 private static final byte[] _CLOSE_TAG = toBytes("</"); 428 private static final byte[] _EMPTY_TAG = toBytes("/>"); 429 private static final byte[] _XML_DECL = toBytes("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"); 430 431 // no need to copy 432 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 433 }