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