001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.schematic.internal.io; 017 018import java.io.DataOutput; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.nio.ByteBuffer; 022import java.nio.ByteOrder; 023import java.nio.CharBuffer; 024import java.nio.channels.Channels; 025import java.nio.channels.WritableByteChannel; 026import java.nio.charset.CharsetEncoder; 027import java.nio.charset.CoderResult; 028import java.nio.charset.StandardCharsets; 029import java.text.CharacterIterator; 030import java.text.StringCharacterIterator; 031import java.util.ArrayList; 032import java.util.List; 033 034/** 035 * An implementation of {@link DataOutput} with additional methods needed for writing BSON formatted content. Specifically, this 036 * class reads little-endian byte order (purposefully in violation of the DataInput interface specification) and provides a way to 037 * read C-style strings (where the length is not known up front but instead contain all characters until the zero-byte 038 * terminator), which are commonly used within the BSON specification. 039 * 040 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 041 */ 042public class BsonDataOutput implements DataOutput { 043 044 private static final int DEFAULT_BUFFER_SIZE = 1048 * 8; 045 046 private final List<ByteBuffer> buffers = new ArrayList<>(); 047 private final int bufferSize; 048 private final byte[] bytes = new byte[8]; 049 private int position = 0; 050 private int size = 0; 051 private ByteBuffer remainderBuffer; 052 053 public BsonDataOutput() { 054 this.bufferSize = DEFAULT_BUFFER_SIZE; 055 } 056 057 protected ByteBuffer getBufferFor( int position ) { 058 int bufferIndex = position / bufferSize; 059 while (bufferIndex >= buffers.size()) { 060 buffers.add(newByteBuffer(bufferSize)); 061 } 062 return buffers.get(bufferIndex); 063 } 064 065 protected ByteBuffer newByteBuffer( int bufferSize ) { 066 return ByteBuffer.allocate(bufferSize).order(ByteOrder.LITTLE_ENDIAN); 067 } 068 069 protected void updateSize( int newSize ) { 070 if (size < newSize) { 071 size = newSize; 072 } 073 } 074 075 public int size() { 076 return size; 077 } 078 079 @Override 080 public void writeByte( int value ) { 081 writeByte(position, value); 082 position += 1; 083 } 084 085 public void writeByte( int position, 086 int value ) { 087 updateSize(position + 1); 088 ByteBuffer buffer = getBufferFor(position); 089 int index = position % bufferSize; 090 buffer.put(index, (byte)value); 091 } 092 093 @Override 094 public void write( byte[] b ) { 095 write(position, b, 0, b.length); 096 position += b.length; 097 } 098 099 @Override 100 public void write( byte[] b, 101 int off, 102 int len ) { 103 write(position, b, off, len); 104 position += len; 105 } 106 107 public void write( int position, 108 byte[] value, 109 int offset, 110 int length ) { 111 updateSize(position + length); 112 ByteBuffer buffer = getBufferFor(position); 113 int index = position % bufferSize; 114 for (int i = offset; i != length; ++i) { 115 byte b = value[i]; 116 if (index == bufferSize) { 117 // We have to use the next buffer ... 118 buffer = getBufferFor(position); 119 index = position % bufferSize; 120 } 121 buffer.put(index, b); 122 ++index; 123 ++position; 124 } 125 } 126 127 @Override 128 public void write( int b ) { 129 writeByte((byte) b); 130 } 131 132 @Override 133 public void writeBoolean( boolean value ) { 134 writeBoolean(position, value); 135 position += 1; 136 } 137 138 public void writeBoolean( int position, 139 boolean value ) { 140 updateSize(position + 1); 141 ByteBuffer buffer = getBufferFor(position); 142 int index = position % bufferSize; 143 buffer.put(index, value ? (byte)1 : (byte)0); 144 } 145 146 @Override 147 public void writeChar( int value ) { 148 writeChar(position, value); 149 position += 2; 150 } 151 152 public void writeChar( int position, 153 int value ) { 154 updateSize(position + 2); 155 ByteBuffer buffer = getBufferFor(position); 156 int index = position % bufferSize; 157 if (buffer.limit() - index >= 2) { 158 // There's enough room to write on the buffer ... 159 buffer.putChar(index, (char)value); 160 } else { 161 // The value will have to span buffers ... 162 bytes[0] = (byte)value; 163 bytes[1] = (byte)(value >> 8); 164 write(position, bytes, 0, 2); 165 } 166 } 167 168 @Override 169 public void writeInt( int value ) { 170 writeInt(position, value); 171 position += 4; 172 } 173 174 public void writeInt( int position, 175 int value ) { 176 updateSize(position + 4); 177 ByteBuffer buffer = getBufferFor(position); 178 int index = position % bufferSize; 179 if (buffer.limit() - index >= 4) { 180 // There's enough room to write on the buffer ... 181 buffer.putInt(index, value); 182 } else { 183 // The value will have to span buffers ... 184 bytes[0] = (byte)value; 185 bytes[1] = (byte)(value >> 8); 186 bytes[2] = (byte)(value >> 16); 187 bytes[3] = (byte)(value >> 24); 188 write(position, bytes, 0, 4); 189 } 190 } 191 192 @Override 193 public void writeShort( int value ) { 194 writeShort(position, value); 195 position += 2; 196 } 197 198 public void writeShort( int position, 199 int value ) { 200 updateSize(position + 2); 201 ByteBuffer buffer = getBufferFor(position); 202 int index = position % bufferSize; 203 if (buffer.limit() - index >= 2) { 204 // There's enough room to write on the buffer ... 205 buffer.putShort(index, (short)value); 206 } else { 207 // The value will have to span buffers ... 208 bytes[0] = (byte)value; 209 bytes[1] = (byte)(value >> 8); 210 write(position, bytes, 0, 2); 211 } 212 } 213 214 @Override 215 public void writeLong( long value ) { 216 writeLong(position, value); 217 position += 8; 218 } 219 220 public void writeLong( int position, 221 long value ) { 222 updateSize(position + 8); 223 ByteBuffer buffer = getBufferFor(position); 224 int index = position % bufferSize; 225 if (buffer.limit() - index >= 8) { 226 // There's enough room to write on the buffer ... 227 buffer.putLong(index, value); 228 } else { 229 // The value will have to span buffers ... 230 bytes[0] = (byte)value; 231 bytes[1] = (byte)(value >> 8); 232 bytes[2] = (byte)(value >> 16); 233 bytes[3] = (byte)(value >> 24); 234 bytes[4] = (byte)(value >> 32); 235 bytes[5] = (byte)(value >> 40); 236 bytes[6] = (byte)(value >> 48); 237 bytes[7] = (byte)(value >> 56); 238 write(position, bytes, 0, 8); 239 } 240 } 241 242 @Override 243 public void writeFloat( float value ) { 244 writeFloat(position, value); 245 position += 4; 246 } 247 248 public void writeFloat( int position, 249 float value ) { 250 writeInt(position, Float.floatToRawIntBits(value)); 251 } 252 253 @Override 254 public void writeDouble( double value ) { 255 writeDouble(position, value); 256 position += 8; 257 } 258 259 public void writeDouble( int position, 260 double value ) { 261 writeLong(position, Double.doubleToRawLongBits(value)); 262 } 263 264 /** 265 * Writes a string to the output stream. For every character in the string <code>s</code>, taken in order, one byte is written 266 * to the output stream. If <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown. 267 * <p> 268 * If <code>s.length</code> is zero, then no bytes are written. Otherwise, the character <code>s[0]</code> is written first, 269 * then <code>s[1]</code>, and so on; the last character written is <code>s[s.length-1]</code>. For each character, one byte 270 * is written, the low-order byte, in exactly the manner of the <code>writeByte</code> method . The high-order eight bits of 271 * each character in the string are ignored. 272 * 273 * @param str the string value to be written. 274 * @deprecated The semantics of {@code writeBytes(String s)} are considered dangerous. Please use {@link #writeUTF(String s)}, 275 * {@link #writeChars(String s)} or another write method instead. 276 */ 277 @Deprecated 278 @Override 279 public void writeBytes( String str ) { 280 CharacterIterator iter = new StringCharacterIterator(str); 281 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { 282 writeByte(c); 283 } 284 } 285 286 /** 287 * Writes every character in the string <code>s</code>, to the output stream, in order, two bytes per character. If 288 * <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown. If <code>s.length</code> is zero, then 289 * no characters are written. Otherwise, the character <code>s[0]</code> is written first, then <code>s[1]</code>, and so on; 290 * the last character written is <code>s[s.length-1]</code>. For each character, two bytes are actually written, low-order 291 * byte first, in exactly the manner of the <code>writeChar</code> method. 292 * 293 * @param str the string value to be written. 294 */ 295 @Override 296 public void writeChars( String str ) { 297 CharacterIterator iter = new StringCharacterIterator(str); 298 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { 299 writeChar(c); 300 } 301 } 302 303 @Override 304 public void writeUTF( String str ) { 305 int numBytesWritten = 0; 306 int numBytesPosition = this.position; 307 // Write a placeholder for the length ... 308 writeShort(numBytesPosition, numBytesWritten); 309 numBytesWritten = writeUTF(position, str); 310 position += numBytesWritten; 311 // Now write the real number of bytes written ... 312 writeShort(numBytesPosition, numBytesWritten); 313 } 314 315 /** 316 * Writes the <a href="DataInput.html#modified-utf-8">modified UTF-8</a> representation of every character in the string 317 * <code>s</code>. <i>This is similar to the standard {@link #writeUTF(String)} but without the leading two byte length.</i> 318 * If <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown. Each character in the string 319 * <code>s</code> is converted to a group of one, two, or three bytes, depending on the value of the character. 320 * <p> 321 * If a character <code>c</code> is in the range <code>\u0001</code> through <code>\u007f</code>, it is represented by 322 * one byte: 323 * <p> 324 * 325 * <pre> 326 * (byte)c 327 * </pre> 328 * <p> 329 * If a character <code>c</code> is <code>\u0000</code> or is in the range <code>\u0080</code> through 330 * <code>\u07ff</code>, then it is represented by two bytes, to be written in the order shown: 331 * <p> 332 * 333 * <pre> 334 * <code> 335 * (byte)(0xc0 | (0x1f & (c >> 6))) 336 * (byte)(0x80 | (0x3f & c)) 337 * </code> 338 * </pre> 339 * <p> 340 * If a character <code>c</code> is in the range <code>\u0800</code> through <code>uffff</code>, then it is represented by 341 * three bytes, to be written in the order shown: 342 * <p> 343 * 344 * <pre> 345 * <code> 346 * (byte)(0xe0 | (0x0f & (c >> 12))) 347 * (byte)(0x80 | (0x3f & (c >> 6))) 348 * (byte)(0x80 | (0x3f & c)) 349 * </code> 350 * </pre> 351 * <p> 352 * First, the total number of bytes needed to represent all the characters of <code>s</code> is calculated. If this number is 353 * larger than <code>65535</code>, then a <code>UTFDataFormatException</code> is thrown. Otherwise, this length is written to 354 * the output stream in exactly the manner of the <code>writeShort</code> method; after this, the one-, two-, or three-byte 355 * representation of each character in the string <code>s</code> is written. 356 * <p> 357 * The bytes written by this method may be read by the <code>readUTF</code> method of interface <code>DataInput</code> , which 358 * will then return a <code>String</code> equal to <code>s</code>. 359 * 360 * @param str the string value to be written. 361 * @return the number of bytes written 362 */ 363 public int writeUTFString( String str ) { 364 int numBytesWritten = writeUTF(position, str); 365 position += numBytesWritten; 366 return numBytesWritten; 367 } 368 369 public int writeUTF( int position, 370 String str ) { 371 CharBuffer chars = CharBuffer.wrap(str); 372 CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); 373 ByteBuffer output = getBufferFor(position); 374 int index = position % bufferSize; 375 int newPosition = position; 376 output.position(index); 377 try { 378 while (chars.hasRemaining()) { 379 CoderResult result = encoder.encode(chars, output, true); 380 381 if (output == remainderBuffer) { 382 // There is some remainder from the previous iteration ... 383 output.flip(); 384 while (output.remaining() > 0) { 385 writeByte(newPosition, output.get()); 386 ++newPosition; 387 } 388 } else { 389 // Calculate the new position based upon where we started ... 390 newPosition += output.position() - index; 391 } 392 393 if (result.isError()) { 394 // Some problem occurred ... 395 result.throwException(); 396 } else if (result.isOverflow()) { 397 // Unable to write all of the content to the output buffer, so there's still some characters left ... 398 if (output.remaining() > 0) { 399 400 // We ran out of space in 'buffer', so go again with a small 'remainder' buffer ... 401 if (remainderBuffer != null) { 402 // There is one, so clear it out and prepare it for use ... 403 remainderBuffer.clear(); 404 } else { 405 // There is none, so allocate one for this object ... 406 remainderBuffer = ByteBuffer.allocate(4); 407 } 408 output = remainderBuffer; 409 index = 0; 410 } else { 411 // We ran out of space by fully utilizing this buffer, so just go to the next buffer ... 412 output = getBufferFor(newPosition); 413 index = newPosition % bufferSize; 414 output.position(index); 415 } 416 } 417 } 418 } catch (Exception e) { 419 throw new RuntimeException("Unable to encode string", e); 420 } 421 updateSize(newPosition); 422 return newPosition - position; 423 } 424 425 /** 426 * Write all content to the supplied stream. 427 * 428 * @param stream the stream to which the content is to be written. 429 * @throws IOException if there is a problem writing to the supplied stream 430 */ 431 public void writeTo( OutputStream stream ) throws IOException { 432 writeTo(Channels.newChannel(stream)); 433 } 434 435 /** 436 * Write all content to the supplied channel. 437 * 438 * @param channel the channel to which the content is to be written. 439 * @throws IOException if there is a problem writing to the supplied stream 440 */ 441 public void writeTo( WritableByteChannel channel ) throws IOException { 442 int numberOfBytesToWrite = size; 443 for (ByteBuffer buffer : buffers) { 444 if (buffer == null) { 445 // already flushed 446 continue; 447 } 448 int numBytesInBuffer = Math.min(numberOfBytesToWrite, bufferSize); 449 buffer.position(numBytesInBuffer); 450 buffer.flip(); 451 channel.write(buffer); 452 numberOfBytesToWrite -= numBytesInBuffer; 453 } 454 buffers.clear(); 455 } 456}