xref: /aosp_15_r20/external/leakcanary2/shark-hprof/src/main/java/shark/RandomAccessHprofReader.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 package shark
2 
3 import okio.Buffer
4 import java.io.Closeable
5 import java.io.File
6 
7 /**
8  * Reads records in a Hprof source, one at a time with a specific position and size.
9  * Call [openReaderFor] to obtain a new instance.
10  */
11 class RandomAccessHprofReader private constructor(
12   private val source: RandomAccessSource,
13   hprofHeader: HprofHeader
14 ) : Closeable {
15   private val buffer = Buffer()
16   private val reader = HprofRecordReader(hprofHeader, buffer)
17 
18   /**
19    * Loads [recordSize] bytes at [recordPosition] into the buffer that backs [HprofRecordReader]
20    * then calls [withRecordReader] with that reader as a receiver. [withRecordReader] is expected
21    * to use the receiver reader to read one record of exactly [recordSize] bytes.
22    * @return the results from [withRecordReader]
23    */
readRecordnull24   fun <T> readRecord(
25     recordPosition: Long,
26     recordSize: Long,
27     withRecordReader: HprofRecordReader.() -> T
28   ): T {
29     require(recordSize > 0L) {
30       "recordSize $recordSize must be > 0"
31     }
32     var mutablePos = recordPosition
33     var mutableByteCount = recordSize
34 
35     while (mutableByteCount > 0L) {
36       val bytesRead = source.read(buffer, mutablePos, mutableByteCount)
37       check(bytesRead > 0) {
38         "Requested $mutableByteCount bytes after reading ${mutablePos - recordPosition}, got 0 bytes instead."
39       }
40       mutablePos += bytesRead
41       mutableByteCount -= bytesRead
42     }
43     return withRecordReader(reader).apply {
44       check(buffer.size() == 0L) {
45         "Buffer not fully consumed: ${buffer.size()} bytes left"
46       }
47     }
48   }
49 
closenull50   override fun close() {
51     source.close()
52   }
53 
54   companion object {
55 
openReaderFornull56     fun openReaderFor(
57       hprofFile: File,
58       hprofHeader: HprofHeader = HprofHeader.parseHeaderOf(hprofFile)
59     ): RandomAccessHprofReader {
60       val sourceProvider = FileSourceProvider(hprofFile)
61       return openReaderFor(sourceProvider, hprofHeader)
62     }
63 
openReaderFornull64     fun openReaderFor(
65       hprofSourceProvider: RandomAccessSourceProvider,
66       hprofHeader: HprofHeader = hprofSourceProvider.openRandomAccessSource()
67         .use { HprofHeader.parseHeaderOf(it.asStreamingSource()) }
68     ): RandomAccessHprofReader {
69       return RandomAccessHprofReader(hprofSourceProvider.openRandomAccessSource(), hprofHeader)
70     }
71   }
72 }