1+ package org .hdf5javalib .hdffile .infrastructure .fractalheap .gemini ;
2+
3+ import java .io .IOException ;
4+ import java .nio .channels .SeekableByteChannel ;
5+ import java .util .Arrays ;
6+ import java .util .HashMap ;
7+ import java .util .Map ;
8+
9+ //##############################################################################
10+ //### FRACTAL HEAP STRUCTURES
11+ //##############################################################################
12+ public class FractalHeap {
13+ public final FractalHeapHeader header ;
14+
15+ // The rootBlock object is removed. This class is now a "handle".
16+ // private final Object rootBlock; // REMOVED
17+
18+ // Context needed for object retrieval
19+ private final SeekableByteChannel channel ;
20+ private final int sizeOfOffsets ;
21+ private final int sizeOfLengths ;
22+ private final int maxDirectRows ;
23+
24+ // Cache to store blocks that have already been read from disk.
25+ // Key: Block Address, Value: Block Object (Direct or Indirect)
26+ private final Map <Long , Object > blockCache ;
27+
28+ /**
29+ * Helper class to return both address and size of a located direct block.
30+ */
31+ private static class DirectBlockInfo {
32+ final long address ;
33+ final long size ;
34+
35+ DirectBlockInfo (long address , long size ) {
36+ this .address = address ;
37+ this .size = size ;
38+ }
39+ }
40+
41+ // MODIFIED CONSTRUCTOR: No longer accepts a rootBlock object.
42+ public FractalHeap (FractalHeapHeader header , SeekableByteChannel channel , int sizeOfOffsets , int sizeOfLengths ) {
43+ this .header = header ;
44+ this .channel = channel ;
45+ this .sizeOfOffsets = sizeOfOffsets ;
46+ this .sizeOfLengths = sizeOfLengths ;
47+ this .blockCache = new HashMap <>();
48+
49+ // Pre-calculate max rows for direct blocks in any indirect block
50+ int log2MaxDirect = Hdf5Utils .ceilLog2 (header .maximumDirectBlockSize );
51+ int log2Start = Hdf5Utils .ceilLog2 (header .startingBlockSize );
52+ this .maxDirectRows = (log2MaxDirect - log2Start ) + 2 ;
53+ }
54+
55+ public byte [] getObject (byte [] rawId ) throws IOException {
56+ if (header .addressOfRootBlock == Hdf5Utils .UNDEFINED_ADDRESS ) {
57+ throw new IOException ("Cannot get object; fractal heap has no root block." );
58+ }
59+
60+ ParsedHeapId id = new ParsedHeapId (rawId , this .header );
61+
62+
63+ switch (id .type ) {
64+ case 0 : // Managed Object
65+ return findManagedObject (id );
66+ case 1 : // Huge Object
67+ throw new UnsupportedOperationException ("Huge object retrieval not implemented." );
68+ case 2 : // Tiny Object
69+ int headerBits = 8 ; // type(2), version(2), reserved(4)
70+ long minOfMaxs = Math .min (header .maximumDirectBlockSize , header .maximumSizeOfManagedObjects );
71+ if (minOfMaxs < (1 << 8 )) {
72+ headerBits += 8 ;
73+ } else if (minOfMaxs < (1 << 16 )) {
74+ headerBits += 16 ;
75+ } else if (minOfMaxs < (1 << 24 )) {
76+ headerBits += 24 ;
77+ } else {
78+ headerBits += 32 ;
79+ }
80+ int dataOffsetInIdBytes = (headerBits + 7 ) / 8 ;
81+ return Arrays .copyOfRange (rawId , dataOffsetInIdBytes , dataOffsetInIdBytes + id .length );
82+ default :
83+ throw new IOException ("Unknown heap object type: " + id .type );
84+ }
85+ }
86+
87+ private byte [] findManagedObject (ParsedHeapId id ) throws IOException {
88+ // 1. Find the address and size of the direct block containing our object's offset.
89+ DirectBlockInfo blockInfo = findDirectBlockForOffset (id .offset );
90+ if (blockInfo .address == Hdf5Utils .UNDEFINED_ADDRESS ) {
91+ throw new IOException ("Could not locate direct block for heap offset: " + id .offset );
92+ }
93+
94+ // 2. Read that specific direct block USING THE CACHED HELPER.
95+ FractalHeapDirectBlock directBlock = getDirectBlock (blockInfo .address , blockInfo .size );
96+
97+ // 3. The heap ID offset is from the start of the heap. The block's offset is also from the start of the heap.
98+ // We need the offset *within* this block's data array.
99+ long offsetInBlock = id .offset - directBlock .blockOffset ;
100+
101+ // 4. Sanity checks.
102+ if (offsetInBlock < 0 || offsetInBlock + id .length > directBlock .objectData .length ) {
103+ throw new IOException ("Heap ID points outside of the located direct block's bounds. HeapID: " + id +
104+ ", Block Offset: " + directBlock .blockOffset + ", Calculated Offset in Block: " + offsetInBlock );
105+ }
106+
107+ // 5. Extract and return the object's data.
108+ return Arrays .copyOfRange (directBlock .objectData , (int ) offsetInBlock , (int ) (offsetInBlock + id .length ));
109+ }
110+
111+ private DirectBlockInfo findDirectBlockForOffset (long targetOffset ) throws IOException {
112+ if (header .currentRowsInRootIndirectBlock == 0 ) {
113+ // Case 1: The root is a direct block.
114+ if (targetOffset < header .startingBlockSize ) {
115+ return new DirectBlockInfo (header .addressOfRootBlock , header .startingBlockSize );
116+ } else {
117+ throw new IOException ("Target offset " + targetOffset + " is out of bounds for a direct root block of size " + header .startingBlockSize );
118+ }
119+ } else {
120+ // Case 2: The root is an indirect block.
121+ return findAddressInIndirectBlock (header .addressOfRootBlock , header .currentRowsInRootIndirectBlock , targetOffset );
122+ }
123+ }
124+
125+ private DirectBlockInfo findAddressInIndirectBlock (long indirectBlockAddr , int rows , long targetOffset ) throws IOException {
126+ // Read the indirect block USING THE CACHED HELPER.
127+ FractalHeapIndirectBlock indirectBlock = getIndirectBlock (indirectBlockAddr , rows );
128+
129+ for (int i = 0 ; i < indirectBlock .childDirectBlockAddresses .length ; i ++) {
130+ long childStartOffset = indirectBlock .blockOffsets [i ];
131+
132+ int row = i / header .tableWidth ;
133+ long childBlockSize = header .startingBlockSize * (1L << row );
134+ long childEndOffset = childStartOffset + childBlockSize ;
135+
136+ if (targetOffset >= childStartOffset && targetOffset < childEndOffset ) {
137+ long childAddress = indirectBlock .childDirectBlockAddresses [i ];
138+ if (childAddress == Hdf5Utils .UNDEFINED_ADDRESS ) {
139+ throw new IOException ("Found entry for offset " + targetOffset + " but the block address is undefined." );
140+ }
141+ return new DirectBlockInfo (childAddress , childBlockSize );
142+ }
143+ }
144+
145+ if (indirectBlock .childIndirectBlockAddresses .length > 0 ) {
146+ throw new UnsupportedOperationException ("Searching for objects in nested indirect blocks is not yet implemented." );
147+ }
148+
149+ throw new IOException ("Failed to find a direct block containing offset " + targetOffset );
150+ }
151+
152+ // --- Caching Helper Methods ---
153+
154+ private FractalHeapDirectBlock getDirectBlock (long address , long size ) throws IOException {
155+ return (FractalHeapDirectBlock ) blockCache .computeIfAbsent (address , addr -> {
156+ try {
157+ return FractalHeapDirectBlock .read (channel , (Long ) addr , size , header , sizeOfOffsets );
158+ } catch (IOException e ) {
159+ // Lambda expressions can't throw checked exceptions directly, so we wrap them.
160+ throw new RuntimeException (e );
161+ }
162+ });
163+ }
164+
165+ private FractalHeapIndirectBlock getIndirectBlock (long address , int rows ) throws IOException {
166+ try {
167+ return (FractalHeapIndirectBlock ) blockCache .computeIfAbsent (address , addr -> {
168+ try {
169+ return FractalHeapIndirectBlock .read (channel , (Long ) addr , rows , header , sizeOfOffsets , sizeOfLengths );
170+ } catch (IOException e ) {
171+ throw new RuntimeException (e );
172+ }
173+ });
174+ } catch (RuntimeException e ) {
175+ // Unwrap the original IOException
176+ if (e .getCause () instanceof IOException ) {
177+ throw (IOException ) e .getCause ();
178+ }
179+ throw e ;
180+ }
181+ }
182+ }
0 commit comments