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 }