1 /* 2 * Copyright (c) 2016, 2018, 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 jdk.jfr.internal.consumer; 27 28 import java.io.DataInput; 29 import java.io.EOFException; 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.RandomAccessFile; 33 import java.nio.charset.Charset; 34 35 public final class RecordingInput implements DataInput, AutoCloseable { 36 37 public static final byte STRING_ENCODING_NULL = 0; 38 public static final byte STRING_ENCODING_EMPTY_STRING = 1; 39 public static final byte STRING_ENCODING_CONSTANT_POOL = 2; 40 public static final byte STRING_ENCODING_UTF8_BYTE_ARRAY = 3; 41 public static final byte STRING_ENCODING_CHAR_ARRAY = 4; 42 public static final byte STRING_ENCODING_LATIN1_BYTE_ARRAY = 5; 43 44 private final static int DEFAULT_BLOCK_SIZE = 16 * 1024 * 1024; 45 private final static Charset UTF8 = Charset.forName("UTF-8"); 46 private final static Charset LATIN1 = Charset.forName("ISO-8859-1"); 47 48 private static final class Block { 49 private byte[] bytes = new byte[0]; 50 private long blockPosition; 51 52 boolean contains(long position) { 53 return position >= blockPosition && position < blockPosition + bytes.length; 54 } 55 56 public void read(RandomAccessFile file, int amount) throws IOException { 57 blockPosition = file.getFilePointer(); 58 // reuse byte array, if possible 59 if (amount != bytes.length) { 60 bytes = new byte[amount]; 61 } 62 file.readFully(bytes); 63 } 64 65 public byte get(long position) { 66 return bytes[(int) (position - blockPosition)]; 67 } 68 } 69 70 private final RandomAccessFile file; 71 private final long size; 72 private Block currentBlock = new Block(); 73 private Block previousBlock = new Block(); 74 private long position; 75 private final int blockSize; 76 77 private RecordingInput(File f, int blockSize) throws IOException { 78 this.size = f.length(); 79 this.blockSize = blockSize; 80 this.file = new RandomAccessFile(f, "r"); 81 if (size < 8) { 82 throw new IOException("Not a valid Flight Recorder file. File length is only " + size + " bytes."); 83 } 84 } 85 86 public RecordingInput(File f) throws IOException { 87 this(f, DEFAULT_BLOCK_SIZE); 88 } 89 90 @Override 91 public final byte readByte() throws IOException { 92 if (!currentBlock.contains(position)) { 93 position(position); 94 } 95 return currentBlock.get(position++); 96 } 97 98 @Override 99 public final void readFully(byte[] dest, int offset, int length) throws IOException { 100 // TODO: Optimize, use Arrays.copy if all bytes are in current block 101 // array 102 for (int i = 0; i < length; i++) { 103 dest[i + offset] = readByte(); 104 } 105 } 106 107 @Override 108 public final void readFully(byte[] dst) throws IOException { 109 readFully(dst, 0, dst.length); 110 } 111 112 public final short readRawShort() throws IOException { 113 // copied from java.io.Bits 114 byte b0 = readByte(); 115 byte b1 = readByte(); 116 return (short) ((b1 & 0xFF) + (b0 << 8)); 117 } 118 119 @Override 120 public final double readDouble() throws IOException { 121 // copied from java.io.Bits 122 return Double.longBitsToDouble(readRawLong()); 123 } 124 125 @Override 126 public final float readFloat() throws IOException { 127 // copied from java.io.Bits 128 return Float.intBitsToFloat(readRawInt()); 129 } 130 131 public final int readRawInt() throws IOException { 132 // copied from java.io.Bits 133 byte b0 = readByte(); 134 byte b1 = readByte(); 135 byte b2 = readByte(); 136 byte b3 = readByte(); 137 return ((b3 & 0xFF)) + ((b2 & 0xFF) << 8) + ((b1 & 0xFF) << 16) + ((b0) << 24); 138 } 139 140 public final long readRawLong() throws IOException { 141 // copied from java.io.Bits 142 byte b0 = readByte(); 143 byte b1 = readByte(); 144 byte b2 = readByte(); 145 byte b3 = readByte(); 146 byte b4 = readByte(); 147 byte b5 = readByte(); 148 byte b6 = readByte(); 149 byte b7 = readByte(); 150 return ((b7 & 0xFFL)) + ((b6 & 0xFFL) << 8) + ((b5 & 0xFFL) << 16) + ((b4 & 0xFFL) << 24) + ((b3 & 0xFFL) << 32) + ((b2 & 0xFFL) << 40) + ((b1 & 0xFFL) << 48) + (((long) b0) << 56); 151 } 152 153 public final long position() throws IOException { 154 return position; 155 } 156 157 public final void position(long newPosition) throws IOException { 158 if (!currentBlock.contains(newPosition)) { 159 if (!previousBlock.contains(newPosition)) { 160 if (newPosition > size()) { 161 throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size() + " bytes."); 162 } 163 long blockStart = trimToFileSize(calculateBlockStart(newPosition)); 164 file.seek(blockStart); 165 // trim amount to file size 166 long amount = Math.min(size() - blockStart, blockSize); 167 previousBlock.read(file, (int) amount); 168 } 169 // swap previous and current 170 Block tmp = currentBlock; 171 currentBlock = previousBlock; 172 previousBlock = tmp; 173 } 174 position = newPosition; 175 } 176 177 private final long trimToFileSize(long position) throws IOException { 178 return Math.min(size(), Math.max(0, position)); 179 } 180 181 private final long calculateBlockStart(long newPosition) { 182 // align to end of current block 183 if (currentBlock.contains(newPosition - blockSize)) { 184 return currentBlock.blockPosition + currentBlock.bytes.length; 185 } 186 // align before current block 187 if (currentBlock.contains(newPosition + blockSize)) { 188 return currentBlock.blockPosition - blockSize; 189 } 190 // not near current block, pick middle 191 return newPosition - blockSize / 2; 192 } 193 194 public final long size() throws IOException { 195 return size; 196 } 197 198 public final void close() throws IOException { 199 file.close(); 200 } 201 202 @Override 203 public final int skipBytes(int n) throws IOException { 204 long position = position(); 205 position(position + n); 206 return (int) (position() - position); 207 } 208 209 @Override 210 public final boolean readBoolean() throws IOException { 211 return readByte() != 0; 212 } 213 214 @Override 215 public int readUnsignedByte() throws IOException { 216 return readByte() & 0x00FF; 217 } 218 219 @Override 220 public int readUnsignedShort() throws IOException { 221 return readShort() & 0xFFFF; 222 } 223 224 @Override 225 public final String readLine() throws IOException { 226 throw new UnsupportedOperationException(); 227 } 228 229 // NOTE, this method should really be called readString 230 // but can't be renamed without making RecordingInput a 231 // public class. 232 // 233 // This method DOES Not read as expected (s2 + utf8 encoded character) 234 // instead it read: 235 // byte encoding 236 // int size 237 // data (byte or char) 238 // 239 // where encoding 240 // 241 // 0, means null 242 // 1, means UTF8 encoded byte array 243 // 2, means char array 244 // 3, means latin-1 (ISO-8859-1) encoded byte array 245 // 4, means "" 246 @Override 247 public String readUTF() throws IOException { 248 return readEncodedString(readByte()); 249 } 250 251 public String readEncodedString(byte encoding) throws IOException { 252 if (encoding == STRING_ENCODING_NULL) { 253 return null; 254 } 255 if (encoding == STRING_ENCODING_EMPTY_STRING) { 256 return ""; 257 } 258 int size = readInt(); 259 if (encoding == STRING_ENCODING_CHAR_ARRAY) { 260 char[] c = new char[size]; 261 for (int i = 0; i < size; i++) { 262 c[i] = readChar(); 263 } 264 return new String(c); 265 } 266 byte[] bytes = new byte[size]; 267 readFully(bytes); // TODO: optimize, check size, and copy only if needed 268 if (encoding == STRING_ENCODING_UTF8_BYTE_ARRAY) { 269 return new String(bytes, UTF8); 270 } 271 272 if (encoding == STRING_ENCODING_LATIN1_BYTE_ARRAY) { 273 return new String(bytes, LATIN1); 274 } 275 throw new IOException("Unknown string encoding " + encoding); 276 } 277 278 @Override 279 public char readChar() throws IOException { 280 return (char) readLong(); 281 } 282 283 @Override 284 public short readShort() throws IOException { 285 return (short) readLong(); 286 } 287 288 @Override 289 public int readInt() throws IOException { 290 return (int) readLong(); 291 } 292 293 @Override 294 public long readLong() throws IOException { 295 // can be optimized by branching checks, but will do for now 296 byte b0 = readByte(); 297 long ret = (b0 & 0x7FL); 298 if (b0 >= 0) { 299 return ret; 300 } 301 int b1 = readByte(); 302 ret += (b1 & 0x7FL) << 7; 303 if (b1 >= 0) { 304 return ret; 305 } 306 int b2 = readByte(); 307 ret += (b2 & 0x7FL) << 14; 308 if (b2 >= 0) { 309 return ret; 310 } 311 int b3 = readByte(); 312 ret += (b3 & 0x7FL) << 21; 313 if (b3 >= 0) { 314 return ret; 315 } 316 int b4 = readByte(); 317 ret += (b4 & 0x7FL) << 28; 318 if (b4 >= 0) { 319 return ret; 320 } 321 int b5 = readByte(); 322 ret += (b5 & 0x7FL) << 35; 323 if (b5 >= 0) { 324 return ret; 325 } 326 int b6 = readByte(); 327 ret += (b6 & 0x7FL) << 42; 328 if (b6 >= 0) { 329 return ret; 330 } 331 int b7 = readByte(); 332 ret += (b7 & 0x7FL) << 49; 333 if (b7 >= 0) { 334 return ret; 335 } 336 int b8 = readByte(); // read last byte raw 337 return ret + (((long) (b8 & 0XFF)) << 56); 338 } 339 }