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.DataInput; 019import java.io.DataInputStream; 020import java.io.EOFException; 021import java.io.IOException; 022import java.lang.ref.SoftReference; 023import java.nio.ByteBuffer; 024import java.nio.CharBuffer; 025import java.nio.charset.CharsetDecoder; 026import java.nio.charset.CoderResult; 027import java.nio.charset.StandardCharsets; 028 029/** 030 * An implementation of {@link DataInput} with additional methods needed for reading BSON formatted content from another DataInput 031 * instance. Specifically, this class reads little-endian byte order (purposefully in violation of the DataInput interface 032 * specification) and provides a way to read C-style strings (where the length is not known up front but instead contain all 033 * characters until the zero-byte terminator), which are commonly used within the BSON specification. 034 * 035 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 036 */ 037public class BsonDataInput implements DataInput { 038 039 /** 040 * A thread-local cache of ByteBuffer and CharBuffer instances. Reusing these buffers results in fewer allocations and less to 041 * garbage collect. Note that this cache has to be thread-local, since {@link BufferCache} is not thread-safe. 042 */ 043 private final static ThreadLocal<SoftReference<BufferCache>> BUFFER_CACHE = new ThreadLocal<>(); 044 045 private static BufferCache getBufferCache() { 046 SoftReference<BufferCache> ref = BUFFER_CACHE.get(); 047 if (ref == null || ref.get() == null) { 048 ref = new SoftReference<>(new BufferCache()); 049 BUFFER_CACHE.set(ref); 050 } 051 return ref.get(); 052 } 053 054 private final DataInput input; 055 private int total = 0; 056 057 public BsonDataInput( DataInput input ) { 058 this.input = input; 059 } 060 061 /** 062 * Returns the number of bytes that have been read from this input. 063 * 064 * @return the total number of bytes already read 065 * @exception IOException if an I/O error occurs. 066 */ 067 public int getTotalBytesRead() throws IOException { 068 return total; 069 } 070 071 @Override 072 public final int readUnsignedByte() throws IOException { 073 int b1 = input.readUnsignedByte(); 074 if (b1 == -1) { 075 throw new EOFException(); 076 } 077 ++total; 078 return (byte)b1; 079 } 080 081 @Override 082 public boolean readBoolean() throws IOException { 083 return readUnsignedByte() != 0; 084 } 085 086 @Override 087 public byte readByte() throws IOException { 088 return (byte)readUnsignedByte(); 089 } 090 091 @Override 092 public char readChar() throws IOException { 093 return (char)readUnsignedShort(); 094 } 095 096 @Override 097 public int readUnsignedShort() throws IOException { 098 byte b1 = (byte)readUnsignedByte(); 099 byte b2 = (byte)readUnsignedByte(); 100 return (b2 & 0xFF) << 8 | (b1 & 0xFF); 101 } 102 103 @Override 104 public short readShort() throws IOException { 105 return (short)readUnsignedShort(); 106 } 107 108 @Override 109 public int readInt() throws IOException { 110 byte b1 = (byte)readUnsignedByte(); 111 byte b2 = (byte)readUnsignedByte(); 112 byte b3 = (byte)readUnsignedByte(); 113 byte b4 = (byte)readUnsignedByte(); 114 return (b4 & 0xFF) << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF); 115 } 116 117 @Override 118 public long readLong() throws IOException { 119 byte b1 = (byte)readUnsignedByte(); 120 byte b2 = (byte)readUnsignedByte(); 121 byte b3 = (byte)readUnsignedByte(); 122 byte b4 = (byte)readUnsignedByte(); 123 byte b5 = (byte)readUnsignedByte(); 124 byte b6 = (byte)readUnsignedByte(); 125 byte b7 = (byte)readUnsignedByte(); 126 byte b8 = (byte)readUnsignedByte(); 127 return (b8 & 0xFFL) << 56 | (b7 & 0xFFL) << 48 | (b6 & 0xFFL) << 40 | (b5 & 0xFFL) << 32 | (b4 & 0xFFL) << 24 128 | (b3 & 0xFFL) << 16 | (b2 & 0xFFL) << 8 | (b1 & 0xFFL); 129 } 130 131 @Override 132 public float readFloat() throws IOException { 133 return Float.intBitsToFloat(readInt()); 134 } 135 136 @Override 137 public double readDouble() throws IOException { 138 return Double.longBitsToDouble(readLong()); 139 } 140 141 @Override 142 public void readFully( byte[] b ) throws IOException { 143 readFully(b, 0, b.length); 144 } 145 146 @Override 147 public void readFully( byte[] b, 148 int off, 149 int len ) throws IOException { 150 while (len > 0) { 151 int read = read(b, off, len); 152 if (read < 0) { 153 throw new EOFException(); 154 } 155 len -= read; 156 off += read; 157 } 158 } 159 160 protected int read( byte[] b, 161 int off, 162 int len ) throws IOException { 163 if (b == null) { 164 throw new NullPointerException(); 165 } else if (off < 0 || len < 0 || len > b.length - off) { 166 throw new IndexOutOfBoundsException(); 167 } else if (len == 0) { 168 return 0; 169 } 170 171 int i = 0; 172 try { 173 for ( ; i < len; i++) { 174 b[off + i] = read(); 175 } 176 } catch (EOFException ee) { 177 return -1; 178 } 179 return i; 180 } 181 182 protected final byte read() throws IOException { 183 byte result = input.readByte(); 184 ++total; 185 return result; 186 } 187 188 @Override 189 public int skipBytes( int n ) throws IOException { 190 if (n <= 0) return 0; 191 int skipped = input.skipBytes(n); 192 total += skipped; 193 return skipped; 194 } 195 196 @Override 197 public String readLine() { 198 throw new UnsupportedOperationException(); 199 } 200 201 @Override 202 public String readUTF() throws IOException { 203 return DataInputStream.readUTF(this); 204 } 205 206 /** 207 * Read a UTF-8 string with the supplied length or, if the length is not known, the UTF-8 characters until the next zero-byte 208 * value. Note that this is different than the {@link #readUTF() standard way} to read a string, which always expects the 209 * length to be the first value on the stream. 210 * 211 * @param len the number of bytes to read, or -1 if the length is not known and characters should be read until the next 212 * zero-byte string. 213 * @return the read UTF-8 string 214 * @throws IOException 215 */ 216 public String readUTF( int len ) throws IOException { 217 return readUTF(this, len); 218 } 219 220 /** 221 * Utility method to read a UTF-8 string from the supplied DataInput object given the supplied number of bytes to read or, if 222 * the number of bytes is not known, all of the UTF-8 characters until the next zero-byte value. Note that this is different 223 * than the {@link #readUTF() standard way} to read a string, which always expects the length to be the first value on the 224 * stream. 225 * 226 * @param dis the DataInput from which the UTF-8 string is to be read 227 * @param len the number of bytes to read, or -1 if the length is not known and characters should be read until the next 228 * zero-byte string. 229 * @return the read UTF-8 string 230 * @throws IOException if there is a problem reading from the input 231 */ 232 public static String readUTF( DataInput dis, 233 int len ) throws IOException { 234 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 235 BufferCache bufferCache = getBufferCache(); 236 ByteBuffer byteBuf = bufferCache.getByteBuffer(BufferCache.MINIMUM_SIZE); 237 CharBuffer charBuf = bufferCache.getCharBuffer(BufferCache.MINIMUM_SIZE); 238 try { 239 byte[] bytes = byteBuf.array(); 240 while (len != 0 || byteBuf.position() > 0) { 241 // Need to read more bytes ... 242 if (len < 0) { 243 // Read until we come across the zero-byte (or until we fill up 'byteBuf') ... 244 while (byteBuf.remaining() != 0) { 245 byte b = dis.readByte(); 246 if (b == 0x0) { 247 len = 0; // no more bytes to read ... 248 break; 249 } 250 byteBuf.put(b); 251 } 252 // Prepare byteBuf for reading ... 253 byteBuf.flip(); 254 } else if (len == 0) { 255 // Don't read anything, but prepare the byteBuf for reading ... 256 byteBuf.flip(); 257 } else { 258 // We know exactly how much we should read ... 259 int amountToRead = Math.min(len, Math.min(byteBuf.remaining(), charBuf.remaining())); 260 int offset = byteBuf.position(); // may have already read some bytes ... 261 dis.readFully(bytes, offset, amountToRead); 262 // take into account the offset because we might have carry-over bytes from a previous compaction 263 // this happens when decoding multi-byte UTF8 chars 264 byteBuf.limit(amountToRead + offset); 265 byteBuf.rewind(); 266 // Adjust the number of bytes to read ... 267 len -= amountToRead; 268 } 269 270 // We've either read all we need to or as much as we can (given the buffer's limited size), 271 // so decode what we've read ... 272 boolean endOfInput = len == 0; 273 CoderResult result = decoder.decode(byteBuf, charBuf, endOfInput); 274 if (result.isError()) { 275 result.throwException(); 276 } else if (result.isUnderflow()) { 277 // We've not read enough bytes yet, so move the bytes that weren't read to the beginning of the buffer 278 byteBuf.compact(); 279 // and try again ... 280 } 281 if (len > 0 && (charBuf.remaining() == 0 || result.isOverflow())) { 282 // the output buffer was too small or is at its end. 283 // allocate a new one which need to have enough capacity to hold the current one (we're appending buffers) and also to hold 284 // the data from the new iteration. 285 int newBufferIncrement = (len > BufferCache.MINIMUM_SIZE) ? BufferCache.MINIMUM_SIZE : len; 286 CharBuffer newBuffer = bufferCache.getCharBuffer(charBuf.capacity() + newBufferIncrement); 287 // prepare the old buffer for reading ... 288 charBuf.flip(); 289 // and copy the contents ... 290 newBuffer.put(charBuf); 291 // and use the new buffer ... 292 charBuf = newBuffer; 293 } 294 } 295 // We're done, so prepare the character buffer for reading and then convert to a String ... 296 charBuf.flip(); 297 return charBuf.toString(); 298 } finally { 299 // Return the buffers to the cache ... 300 bufferCache.checkin(byteBuf); 301 bufferCache.checkin(charBuf); 302 } 303 } 304}