1+ package org .hdf5javalib .hdffile .infrastructure .fractalheap .archive .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+ // package org.hdf5javalib.hdffile.infrastructure.fractalheap.gemini;
10+ // ... (imports)
11+
12+ public class FractalHeap {
13+ // ... (fields are mostly the same)
14+ public final FractalHeapHeader header ;
15+ private final SeekableByteChannel channel ;
16+ private final int sizeOfOffsets ;
17+ private final int sizeOfLengths ;
18+ private final Map <Long , Object > blockCache ;
19+
20+ // ### REASONING FOR CHANGE ###
21+ // The DirectBlockInfo needs to return not just the address and size of the located
22+ // block, but also its starting offset within the heap's linear address space. This
23+ // is essential for calculating the object's relative position inside the block.
24+ private static class DirectBlockInfo {
25+ final long address ; // File address of the block
26+ final long size ; // Size of the block
27+ final long startOffset ; // The block's starting offset in the heap's linear address space
28+
29+ DirectBlockInfo (long address , long size , long startOffset ) {
30+ this .address = address ;
31+ this .size = size ;
32+ this .startOffset = startOffset ;
33+ }
34+ }
35+
36+ public FractalHeap (FractalHeapHeader header , SeekableByteChannel channel , int sizeOfOffsets , int sizeOfLengths ) {
37+ this .header = header ;
38+ this .channel = channel ;
39+ this .sizeOfOffsets = sizeOfOffsets ;
40+ this .sizeOfLengths = sizeOfLengths ;
41+ this .blockCache = new HashMap <>();
42+ }
43+
44+ // ... (getObject method is fine)
45+ public byte [] getObject (byte [] rawId ) throws IOException {
46+ // ... (existing implementation is good)
47+ if (header .addressOfRootBlock == Hdf5Utils .UNDEFINED_ADDRESS ) {
48+ throw new IOException ("Cannot get object; fractal heap has no root block." );
49+ }
50+
51+ ParsedHeapId id = new ParsedHeapId (rawId , this .header );
52+
53+
54+ switch (id .type ) {
55+ case 0 : // Managed Object
56+ return findManagedObject (id );
57+ case 1 : // Huge Object
58+ throw new UnsupportedOperationException ("Huge object retrieval not implemented." );
59+ case 2 : // Tiny Object
60+ // ... (existing tiny object logic seems plausible)
61+ return null ;
62+ default :
63+ throw new IOException ("Unknown heap object type: " + id .type );
64+ }
65+ }
66+
67+
68+ private byte [] findManagedObject (ParsedHeapId id ) throws IOException {
69+ DirectBlockInfo blockInfo = findDirectBlockForOffset (id .offset );
70+ if (blockInfo .address == Hdf5Utils .UNDEFINED_ADDRESS ) {
71+ throw new IOException ("Could not locate direct block for heap offset: " + id .offset );
72+ }
73+
74+ FractalHeapDirectBlock directBlock = getDirectBlock (blockInfo .address , blockInfo .size );
75+
76+ // ### REASONING FOR CHANGE ###
77+ // The object's offset is absolute (from the start of the heap). The block's
78+ // offset is also absolute. To find the object's position *inside* the block's
79+ // data array, we must subtract the block's starting offset.
80+ // OLD: long offsetInBlock = id.offset;
81+ long offsetInBlock = id .offset - blockInfo .startOffset ;
82+
83+ if (offsetInBlock < 0 || offsetInBlock + id .length > directBlock .objectData .length ) {
84+ throw new IOException ("Heap ID points outside of the located direct block's bounds. HeapID offset: " + id .offset +
85+ ", Block Start Offset: " + blockInfo .startOffset + ", Calculated Offset in Block: " + offsetInBlock +
86+ ", Block Data Length: " + directBlock .objectData .length );
87+ }
88+
89+ return Arrays .copyOfRange (directBlock .objectData , (int ) offsetInBlock , (int ) (offsetInBlock + id .length ));
90+ }
91+
92+ private DirectBlockInfo findDirectBlockForOffset (long targetOffset ) throws IOException {
93+ if (header .currentRowsInRootIndirectBlock == 0 ) {
94+ if (targetOffset < header .startingBlockSize ) {
95+ // The root is a direct block. Its starting offset in the heap is 0.
96+ return new DirectBlockInfo (header .addressOfRootBlock , header .startingBlockSize , 0 );
97+ } else {
98+ throw new IOException ("Target offset is out of bounds for a direct root block." );
99+ }
100+ } else {
101+ // The root is an indirect block.
102+ return findAddressInIndirectBlock (header .addressOfRootBlock , header .currentRowsInRootIndirectBlock , targetOffset );
103+ }
104+ }
105+
106+ private DirectBlockInfo findAddressInIndirectBlock (long indirectBlockAddr , int rows , long targetOffset ) throws IOException {
107+ FractalHeapIndirectBlock indirectBlock = getIndirectBlock (indirectBlockAddr , rows );
108+
109+ // ### REASONING FOR CHANGE ###
110+ // This is the core logic fix. We start with the single known offset for the
111+ // indirect block, which covers its first child. Then, we loop and cumulatively
112+ // add the size of each child block to find the starting offset of the next one.
113+ long currentBlockStartOffset = indirectBlock .blockOffset ;
114+
115+ for (int i = 0 ; i < indirectBlock .childDirectBlockAddresses .length ; i ++) {
116+ // The row determines the size of the block this entry points to.
117+ int row = i / header .tableWidth ;
118+ long childBlockSize = header .startingBlockSize * (1L << row );
119+
120+ // Check if our target offset falls within the range of THIS child block.
121+ if (targetOffset >= currentBlockStartOffset && targetOffset < (currentBlockStartOffset + childBlockSize )) {
122+ long childAddress = indirectBlock .childDirectBlockAddresses [i ];
123+ if (childAddress == Hdf5Utils .UNDEFINED_ADDRESS ) {
124+ // This space in the heap is allocated but the block hasn't been written to disk.
125+ throw new IOException ("Found entry for offset " + targetOffset + " but the block address is undefined." );
126+ }
127+ // We found it! Return the address, size, and calculated starting offset.
128+ return new DirectBlockInfo (childAddress , childBlockSize , currentBlockStartOffset );
129+ }
130+
131+ // If not, add this block's size to the running offset and check the next entry.
132+ currentBlockStartOffset += childBlockSize ;
133+ }
134+
135+ if (indirectBlock .childIndirectBlockAddresses .length > 0 ) {
136+ // TODO: The same cumulative logic needs to be applied to find which child
137+ // indirect block to descend into. The `currentBlockStartOffset` would continue
138+ // to accumulate across the direct block section first.
139+ throw new UnsupportedOperationException ("Searching for objects in nested indirect blocks is not yet implemented." );
140+ }
141+
142+ throw new IOException ("Failed to find a direct block containing offset " + targetOffset );
143+ }
144+
145+ // --- Caching Helper Methods ---
146+
147+ private FractalHeapDirectBlock getDirectBlock (long address , long size ) throws IOException {
148+ return (FractalHeapDirectBlock ) blockCache .computeIfAbsent (address , addr -> {
149+ try {
150+ return FractalHeapDirectBlock .read (channel , (Long ) addr , size , header , sizeOfOffsets );
151+ } catch (IOException e ) {
152+ // Lambda expressions can't throw checked exceptions directly, so we wrap them.
153+ throw new RuntimeException (e );
154+ }
155+ });
156+ }
157+
158+ private FractalHeapIndirectBlock getIndirectBlock (long address , int rows ) throws IOException {
159+ try {
160+ return (FractalHeapIndirectBlock ) blockCache .computeIfAbsent (address , addr -> {
161+ try {
162+ return FractalHeapIndirectBlock .read (channel , (Long ) addr , rows , header , sizeOfOffsets , sizeOfLengths );
163+ } catch (IOException e ) {
164+ throw new RuntimeException (e );
165+ }
166+ });
167+ } catch (RuntimeException e ) {
168+ // Unwrap the original IOException
169+ if (e .getCause () instanceof IOException ) {
170+ throw (IOException ) e .getCause ();
171+ }
172+ throw e ;
173+ }
174+ }
175+ }
0 commit comments