1 /* 2 * Copyright (c) 2016, 2020, 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.file.Path; 34 35 public final class RecordingInput implements DataInput, AutoCloseable { 36 37 private final static int DEFAULT_BLOCK_SIZE = 64_000; 38 39 private static final class Block { 40 private byte[] bytes = new byte[0]; 41 private long blockPosition; 42 private long blockPositionEnd; 43 contains(long position)44 boolean contains(long position) { 45 return position >= blockPosition && position < blockPositionEnd; 46 } 47 read(RandomAccessFile file, int amount)48 public void read(RandomAccessFile file, int amount) throws IOException { 49 blockPosition = file.getFilePointer(); 50 // reuse byte array, if possible 51 if (amount > bytes.length) { 52 bytes = new byte[amount]; 53 } 54 this.blockPositionEnd = blockPosition + amount; 55 file.readFully(bytes, 0, amount); 56 } 57 get(long position)58 public byte get(long position) { 59 return bytes[(int) (position - blockPosition)]; 60 } 61 reset()62 public void reset() { 63 blockPosition = 0; 64 blockPositionEnd = 0; 65 } 66 } 67 private final int blockSize; 68 private final FileAccess fileAccess; 69 private RandomAccessFile file; 70 private String filename; 71 private Block currentBlock = new Block(); 72 private Block previousBlock = new Block(); 73 private long position; 74 private long size = -1; // Fail fast if setSize(...) has not been called 75 // before parsing 76 RecordingInput(File f, FileAccess fileAccess, int blockSize)77 RecordingInput(File f, FileAccess fileAccess, int blockSize) throws IOException { 78 this.blockSize = blockSize; 79 this.fileAccess = fileAccess; 80 initialize(f); 81 } 82 initialize(File f)83 private void initialize(File f) throws IOException { 84 this.filename = fileAccess.getAbsolutePath(f); 85 this.file = fileAccess.openRAF(f, "r"); 86 this.position = 0; 87 this.size = -1; 88 this.currentBlock.reset(); 89 previousBlock.reset(); 90 if (fileAccess.length(f) < 8) { 91 throw new IOException("Not a valid Flight Recorder file. File length is only " + fileAccess.length(f) + " bytes."); 92 } 93 } 94 RecordingInput(File f, FileAccess fileAccess)95 public RecordingInput(File f, FileAccess fileAccess) throws IOException { 96 this(f, fileAccess, DEFAULT_BLOCK_SIZE); 97 } 98 positionPhysical(long position)99 void positionPhysical(long position) throws IOException { 100 file.seek(position); 101 } 102 readPhysicalByte()103 byte readPhysicalByte() throws IOException { 104 return file.readByte(); 105 } 106 readPhysicalLong()107 long readPhysicalLong() throws IOException { 108 return file.readLong(); 109 } 110 111 @Override readByte()112 public final byte readByte() throws IOException { 113 if (!currentBlock.contains(position)) { 114 position(position); 115 } 116 return currentBlock.get(position++); 117 } 118 119 @Override readFully(byte[] dest, int offset, int length)120 public final void readFully(byte[] dest, int offset, int length) throws IOException { 121 // TODO: Optimize, use Arrays.copy if all bytes are in current block 122 // array 123 for (int i = 0; i < length; i++) { 124 dest[i + offset] = readByte(); 125 } 126 } 127 128 @Override readFully(byte[] dst)129 public final void readFully(byte[] dst) throws IOException { 130 readFully(dst, 0, dst.length); 131 } 132 readRawShort()133 short readRawShort() throws IOException { 134 // copied from java.io.Bits 135 byte b0 = readByte(); 136 byte b1 = readByte(); 137 return (short) ((b1 & 0xFF) + (b0 << 8)); 138 } 139 140 @Override readDouble()141 public double readDouble() throws IOException { 142 // copied from java.io.Bits 143 return Double.longBitsToDouble(readRawLong()); 144 } 145 146 @Override readFloat()147 public float readFloat() throws IOException { 148 // copied from java.io.Bits 149 return Float.intBitsToFloat(readRawInt()); 150 } 151 readRawInt()152 int readRawInt() throws IOException { 153 // copied from java.io.Bits 154 byte b0 = readByte(); 155 byte b1 = readByte(); 156 byte b2 = readByte(); 157 byte b3 = readByte(); 158 return ((b3 & 0xFF)) + ((b2 & 0xFF) << 8) + ((b1 & 0xFF) << 16) + ((b0) << 24); 159 } 160 readRawLong()161 long readRawLong() throws IOException { 162 // copied from java.io.Bits 163 byte b0 = readByte(); 164 byte b1 = readByte(); 165 byte b2 = readByte(); 166 byte b3 = readByte(); 167 byte b4 = readByte(); 168 byte b5 = readByte(); 169 byte b6 = readByte(); 170 byte b7 = readByte(); 171 return ((b7 & 0xFFL)) + ((b6 & 0xFFL) << 8) + ((b5 & 0xFFL) << 16) + ((b4 & 0xFFL) << 24) + ((b3 & 0xFFL) << 32) + ((b2 & 0xFFL) << 40) + ((b1 & 0xFFL) << 48) + (((long) b0) << 56); 172 } 173 position()174 public final long position() { 175 return position; 176 } 177 position(long newPosition)178 public final void position(long newPosition) throws IOException { 179 if (!currentBlock.contains(newPosition)) { 180 if (!previousBlock.contains(newPosition)) { 181 if (newPosition > size) { 182 throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size + " bytes."); 183 } 184 long blockStart = trimToFileSize(calculateBlockStart(newPosition)); 185 file.seek(blockStart); 186 // trim amount to file size 187 long amount = Math.min(size - blockStart, blockSize); 188 previousBlock.read(file, (int) amount); 189 } 190 // swap previous and current 191 Block tmp = currentBlock; 192 currentBlock = previousBlock; 193 previousBlock = tmp; 194 } 195 position = newPosition; 196 } 197 trimToFileSize(long position)198 private final long trimToFileSize(long position) throws IOException { 199 return Math.min(size(), Math.max(0, position)); 200 } 201 calculateBlockStart(long newPosition)202 private final long calculateBlockStart(long newPosition) { 203 // align to end of current block 204 if (currentBlock.contains(newPosition - blockSize)) { 205 return currentBlock.blockPosition + currentBlock.bytes.length; 206 } 207 // align before current block 208 if (currentBlock.contains(newPosition + blockSize)) { 209 return currentBlock.blockPosition - blockSize; 210 } 211 // not near current block, pick middle 212 return newPosition - blockSize / 2; 213 } 214 size()215 long size() { 216 return size; 217 } 218 219 @Override close()220 public void close() throws IOException { 221 RandomAccessFile ra = file; 222 if (ra != null) { 223 ra.close(); 224 } 225 } 226 227 @Override skipBytes(int n)228 public final int skipBytes(int n) throws IOException { 229 long position = position(); 230 position(position + n); 231 return (int) (position() - position); 232 } 233 234 @Override readBoolean()235 public final boolean readBoolean() throws IOException { 236 return readByte() != 0; 237 } 238 239 @Override readUnsignedByte()240 public int readUnsignedByte() throws IOException { 241 return readByte() & 0x00FF; 242 } 243 244 @Override readUnsignedShort()245 public int readUnsignedShort() throws IOException { 246 return readShort() & 0xFFFF; 247 } 248 249 @Override readLine()250 public final String readLine() throws IOException { 251 throw new UnsupportedOperationException(); 252 } 253 254 // NOTE, this method should really be called readString 255 // but can't be renamed without making RecordingInput a 256 // public class. 257 // 258 // This method DOES Not read as expected (s2 + utf8 encoded character) 259 // instead it read: 260 // byte encoding 261 // int size 262 // data (byte or char) 263 // 264 // where encoding 265 // 266 // 0, means null 267 // 1, means UTF8 encoded byte array 268 // 2, means char array 269 // 3, means latin-1 (ISO-8859-1) encoded byte array 270 // 4, means "" 271 @Override readUTF()272 public String readUTF() throws IOException { 273 throw new UnsupportedOperationException("Use StringParser"); 274 } 275 276 @Override readChar()277 public char readChar() throws IOException { 278 return (char) readLong(); 279 } 280 281 @Override readShort()282 public short readShort() throws IOException { 283 return (short) readLong(); 284 } 285 286 @Override readInt()287 public int readInt() throws IOException { 288 return (int) readLong(); 289 } 290 291 @Override readLong()292 public long readLong() throws IOException { 293 final byte[] bytes = currentBlock.bytes; 294 final int index = (int) (position - currentBlock.blockPosition); 295 296 if (index + 8 < bytes.length && index >= 0) { 297 byte b0 = bytes[index]; 298 long ret = (b0 & 0x7FL); 299 if (b0 >= 0) { 300 position += 1; 301 return ret; 302 } 303 int b1 = bytes[index + 1]; 304 ret += (b1 & 0x7FL) << 7; 305 if (b1 >= 0) { 306 position += 2; 307 return ret; 308 } 309 int b2 = bytes[index + 2]; 310 ret += (b2 & 0x7FL) << 14; 311 if (b2 >= 0) { 312 position += 3; 313 return ret; 314 } 315 int b3 = bytes[index + 3]; 316 ret += (b3 & 0x7FL) << 21; 317 if (b3 >= 0) { 318 position += 4; 319 return ret; 320 } 321 int b4 = bytes[index + 4]; 322 ret += (b4 & 0x7FL) << 28; 323 if (b4 >= 0) { 324 position += 5; 325 return ret; 326 } 327 int b5 = bytes[index + 5]; 328 ret += (b5 & 0x7FL) << 35; 329 if (b5 >= 0) { 330 position += 6; 331 return ret; 332 } 333 int b6 = bytes[index + 6]; 334 ret += (b6 & 0x7FL) << 42; 335 if (b6 >= 0) { 336 position += 7; 337 return ret; 338 } 339 int b7 = bytes[index + 7]; 340 ret += (b7 & 0x7FL) << 49; 341 if (b7 >= 0) { 342 position += 8; 343 return ret; 344 } 345 int b8 = bytes[index + 8];// read last byte raw 346 position += 9; 347 return ret + (((long) (b8 & 0XFF)) << 56); 348 } else { 349 return readLongSlow(); 350 } 351 } 352 readLongSlow()353 private long readLongSlow() throws IOException { 354 byte b0 = readByte(); 355 long ret = (b0 & 0x7FL); 356 if (b0 >= 0) { 357 return ret; 358 } 359 360 int b1 = readByte(); 361 ret += (b1 & 0x7FL) << 7; 362 if (b1 >= 0) { 363 return ret; 364 } 365 366 int b2 = readByte(); 367 ret += (b2 & 0x7FL) << 14; 368 if (b2 >= 0) { 369 return ret; 370 } 371 372 int b3 = readByte(); 373 ret += (b3 & 0x7FL) << 21; 374 if (b3 >= 0) { 375 return ret; 376 } 377 378 int b4 = readByte(); 379 ret += (b4 & 0x7FL) << 28; 380 if (b4 >= 0) { 381 return ret; 382 } 383 384 int b5 = readByte(); 385 ret += (b5 & 0x7FL) << 35; 386 if (b5 >= 0) { 387 return ret; 388 } 389 390 int b6 = readByte(); 391 ret += (b6 & 0x7FL) << 42; 392 if (b6 >= 0) { 393 return ret; 394 } 395 396 int b7 = readByte(); 397 ret += (b7 & 0x7FL) << 49; 398 if (b7 >= 0) { 399 return ret; 400 401 } 402 403 int b8 = readByte(); // read last byte raw 404 return ret + (((long) (b8 & 0XFF)) << 56); 405 } 406 setValidSize(long size)407 public void setValidSize(long size) { 408 if (size > this.size) { 409 this.size = size; 410 } 411 } 412 getFileSize()413 public long getFileSize() throws IOException { 414 return file.length(); 415 } 416 getFilename()417 public String getFilename() { 418 return filename; 419 } 420 421 // Purpose of this method is to reuse block cache from a 422 // previous RecordingInput setFile(Path path)423 public void setFile(Path path) throws IOException { 424 try { 425 file.close(); 426 } catch (IOException e) { 427 // perhaps deleted 428 } 429 file = null; 430 initialize(path.toFile()); 431 } 432 433 } 434