2121public abstract class Array extends AbstractNode {
2222
2323 protected CodecPipeline codecPipeline ;
24+ public static final boolean DEFAULT_PARALLELISM = true ;
2425
2526 protected Array (StoreHandle storeHandle ) throws ZarrException {
2627 super (storeHandle );
@@ -88,7 +89,7 @@ public void write(long[] offset, ucar.ma2.Array array, boolean parallel) {
8889 throw new IllegalArgumentException ("'array' needs to have rank '" + metadata .ndim () + "'." );
8990 }
9091
91- int [] shape = array .getShape ();
92+ long [] shape = Utils . toLongArray ( array .getShape () );
9293
9394 final int [] chunkShape = metadata .chunkShape ();
9495 Stream <long []> chunkStream = Arrays .stream (IndexingUtils .computeChunkCoords (metadata .shape , chunkShape , offset , shape ));
@@ -118,8 +119,14 @@ public void write(long[] offset, ucar.ma2.Array array, boolean parallel) {
118119 );
119120 }
120121 writeChunk (chunkCoords , chunkArray );
121- } catch (ZarrException | InvalidRangeException e ) {
122- throw new RuntimeException (e );
122+ } catch (ZarrException e ) {
123+ throw new RuntimeException (
124+ "Failed to write chunk at coordinates " + Arrays .toString (chunkCoords ) +
125+ ": " + e .getMessage (), e );
126+ } catch (InvalidRangeException e ) {
127+ throw new RuntimeException (
128+ "Invalid array range when writing chunk at coordinates " + Arrays .toString (chunkCoords ) +
129+ ": " + e .getMessage (), e );
123130 }
124131 });
125132
@@ -174,6 +181,110 @@ public ucar.ma2.Array readChunk(long[] chunkCoords) throws ZarrException {
174181 return codecPipeline .decode (chunkBytes );
175182 }
176183
184+ /**
185+ * Deletes chunks that are completely outside the new shape and trims boundary chunks.
186+ *
187+ * @param newShape the new shape of the array
188+ * @param parallel utilizes parallelism if true
189+ */
190+ protected void cleanupChunksForResize (long [] newShape , boolean parallel ) {
191+ ArrayMetadata metadata = metadata ();
192+ final int [] chunkShape = metadata .chunkShape ();
193+ final int ndim = metadata .ndim ();
194+ final dev .zarr .zarrjava .core .chunkkeyencoding .ChunkKeyEncoding chunkKeyEncoding = metadata .chunkKeyEncoding ();
195+
196+ // Calculate max valid chunk coordinates for the new shape
197+ long [] newMaxChunkCoords = new long [ndim ];
198+ for (int i = 0 ; i < ndim ; i ++) {
199+ newMaxChunkCoords [i ] = (newShape [i ] + chunkShape [i ] - 1 ) / chunkShape [i ];
200+ }
201+
202+ // Iterate over all possible chunk coordinates in the old shape
203+ long [][] allOldChunkCoords = IndexingUtils .computeChunkCoords (metadata .shape , chunkShape );
204+
205+ Stream <long []> chunkStream = Arrays .stream (allOldChunkCoords );
206+ if (parallel ) {
207+ chunkStream = chunkStream .parallel ();
208+ }
209+
210+ chunkStream .forEach (chunkCoords -> {
211+ boolean isOutsideBounds = false ;
212+ boolean isOnBoundary = false ;
213+
214+ for (int dimIdx = 0 ; dimIdx < ndim ; dimIdx ++) {
215+ if (chunkCoords [dimIdx ] >= newMaxChunkCoords [dimIdx ]) {
216+ isOutsideBounds = true ;
217+ break ;
218+ }
219+ // Check if this chunk is on the boundary (partially outside new shape)
220+ long chunkEnd = (chunkCoords [dimIdx ] + 1 ) * chunkShape [dimIdx ];
221+ if (chunkEnd > newShape [dimIdx ]) {
222+ isOnBoundary = true ;
223+ }
224+ }
225+
226+ String [] chunkKeys = chunkKeyEncoding .encodeChunkKey (chunkCoords );
227+ StoreHandle chunkHandle = storeHandle .resolve (chunkKeys );
228+
229+ if (isOutsideBounds ) {
230+ // Delete chunk that is completely outside
231+ chunkHandle .delete ();
232+ } else if (isOnBoundary ) {
233+ // Trim boundary chunk - read, clear out-of-bounds data, write back
234+ try {
235+ trimBoundaryChunk (chunkCoords , newShape , chunkShape );
236+ } catch (ZarrException e ) {
237+ throw new RuntimeException (e );
238+ }
239+ }
240+ });
241+ }
242+
243+ /**
244+ * Trims a boundary chunk by reading it, clearing the out-of-bounds portion, and writing it back.
245+ *
246+ * @param chunkCoords the coordinates of the chunk to trim
247+ * @param newShape the new shape of the array
248+ * @param chunkShape the shape of the chunks
249+ * @throws ZarrException if reading or writing the chunk fails
250+ */
251+ protected void trimBoundaryChunk (long [] chunkCoords , long [] newShape , int [] chunkShape ) throws ZarrException {
252+ ArrayMetadata metadata = metadata ();
253+ final int ndim = metadata .ndim ();
254+
255+ // Calculate the valid region within this chunk
256+ int [] validShape = new int [ndim ];
257+ boolean needsTrimming = false ;
258+ for (int dimIdx = 0 ; dimIdx < ndim ; dimIdx ++) {
259+ long chunkStart = chunkCoords [dimIdx ] * chunkShape [dimIdx ];
260+ long chunkEnd = chunkStart + chunkShape [dimIdx ];
261+ if (chunkEnd > newShape [dimIdx ]) {
262+ validShape [dimIdx ] = (int ) (newShape [dimIdx ] - chunkStart );
263+ needsTrimming = true ;
264+ } else {
265+ validShape [dimIdx ] = chunkShape [dimIdx ];
266+ }
267+ }
268+
269+ if (!needsTrimming ) {
270+ return ;
271+ }
272+
273+ // Read the existing chunk
274+ ucar .ma2 .Array chunkData = readChunk (chunkCoords );
275+
276+ // Create a new chunk filled with fill value
277+ ucar .ma2 .Array newChunkData = metadata .allocateFillValueChunk ();
278+
279+ // Copy only the valid region
280+ MultiArrayUtils .copyRegion (
281+ chunkData , new int [ndim ], newChunkData , new int [ndim ], validShape
282+ );
283+
284+ // Write the trimmed chunk back
285+ writeChunk (chunkCoords , newChunkData );
286+ }
287+
177288
178289 /**
179290 * Writes a ucar.ma2.Array into the Zarr array at the beginning of the Zarr array. The shape of
@@ -195,7 +306,7 @@ public void write(ucar.ma2.Array array) {
195306 * @param array the data to write
196307 */
197308 public void write (long [] offset , ucar .ma2 .Array array ) {
198- write (offset , array , false );
309+ write (offset , array , DEFAULT_PARALLELISM );
199310 }
200311
201312 /**
@@ -217,7 +328,7 @@ public void write(ucar.ma2.Array array, boolean parallel) {
217328 */
218329 @ Nonnull
219330 public ucar .ma2 .Array read () throws ZarrException {
220- return read (new long [metadata ().ndim ()], Utils . toIntArray ( metadata ().shape ) );
331+ return read (new long [metadata ().ndim ()], metadata ().shape );
221332 }
222333
223334 /**
@@ -229,8 +340,8 @@ public ucar.ma2.Array read() throws ZarrException {
229340 * @throws ZarrException throws ZarrException if the requested data is outside the array's domain or if the read fails
230341 */
231342 @ Nonnull
232- public ucar .ma2 .Array read (final long [] offset , final int [] shape ) throws ZarrException {
233- return read (offset , shape , false );
343+ public ucar .ma2 .Array read (final long [] offset , final long [] shape ) throws ZarrException {
344+ return read (offset , shape , DEFAULT_PARALLELISM );
234345 }
235346
236347 /**
@@ -241,7 +352,7 @@ public ucar.ma2.Array read(final long[] offset, final int[] shape) throws ZarrEx
241352 */
242353 @ Nonnull
243354 public ucar .ma2 .Array read (final boolean parallel ) throws ZarrException {
244- return read (new long [metadata ().ndim ()], Utils . toIntArray ( metadata ().shape ) , parallel );
355+ return read (new long [metadata ().ndim ()], metadata ().shape , parallel );
245356 }
246357
247358 boolean chunkIsInArray (long [] chunkCoords ) {
@@ -264,7 +375,7 @@ boolean chunkIsInArray(long[] chunkCoords) {
264375 * @throws ZarrException throws ZarrException if the requested data is outside the array's domain or if the read fails
265376 */
266377 @ Nonnull
267- public ucar .ma2 .Array read (final long [] offset , final int [] shape , final boolean parallel ) throws ZarrException {
378+ public ucar .ma2 .Array read (final long [] offset , final long [] shape , final boolean parallel ) throws ZarrException {
268379 ArrayMetadata metadata = metadata ();
269380 if (offset .length != metadata .ndim ()) {
270381 throw new IllegalArgumentException ("'offset' needs to have rank '" + metadata .ndim () + "'." );
@@ -284,7 +395,7 @@ public ucar.ma2.Array read(final long[] offset, final int[] shape, final boolean
284395 }
285396
286397 final ucar .ma2 .Array outputArray = ucar .ma2 .Array .factory (metadata .dataType ().getMA2DataType (),
287- shape );
398+ Utils . toIntArray ( shape ) );
288399 Stream <long []> chunkStream = Arrays .stream (IndexingUtils .computeChunkCoords (metadata .shape , chunkShape , offset , shape ));
289400 if (parallel ) {
290401 chunkStream = chunkStream .parallel ();
@@ -306,19 +417,20 @@ public ucar.ma2.Array read(final long[] offset, final int[] shape, final boolean
306417
307418 final String [] chunkKeys = metadata .chunkKeyEncoding ().encodeChunkKey (chunkCoords );
308419 final StoreHandle chunkHandle = storeHandle .resolve (chunkKeys );
309- if (!chunkHandle .exists ()) {
310- return ;
311- }
420+
312421 if (codecPipeline .supportsPartialDecode ()) {
313422 final ucar .ma2 .Array chunkArray = codecPipeline .decodePartial (chunkHandle ,
314423 Utils .toLongArray (chunkProjection .chunkOffset ), chunkProjection .shape );
315424 MultiArrayUtils .copyRegion (chunkArray , new int [metadata .ndim ()], outputArray ,
316425 chunkProjection .outOffset , chunkProjection .shape
317426 );
318427 } else {
319- MultiArrayUtils .copyRegion (readChunk (chunkCoords ), chunkProjection .chunkOffset ,
320- outputArray , chunkProjection .outOffset , chunkProjection .shape
321- );
428+ ByteBuffer chunkBytes = chunkHandle .read ();
429+ if (chunkBytes != null ) {
430+ MultiArrayUtils .copyRegion (codecPipeline .decode (chunkBytes ), chunkProjection .chunkOffset ,
431+ outputArray , chunkProjection .outOffset , chunkProjection .shape
432+ );
433+ }
322434 }
323435
324436 } catch (ZarrException e ) {
@@ -328,6 +440,46 @@ public ucar.ma2.Array read(final long[] offset, final int[] shape, final boolean
328440 return outputArray ;
329441 }
330442
443+ /**
444+ * Sets a new shape for the Zarr array. Only the metadata is updated by default.
445+ * This method returns a new instance of the Zarr array class and the old instance
446+ * becomes invalid.
447+ *
448+ * @param newShape the new shape of the Zarr array
449+ * @throws ZarrException if the new metadata is invalid
450+ * @throws IOException throws IOException if the new metadata cannot be serialized
451+ */
452+ public Array resize (long [] newShape ) throws ZarrException , IOException {
453+ return resize (newShape , true );
454+ }
455+
456+ /**
457+ * Sets a new shape for the Zarr array. This method returns a new instance of the Zarr array class
458+ * and the old instance becomes invalid.
459+ *
460+ * @param newShape the new shape of the Zarr array
461+ * @param resizeMetadataOnly if true, only the metadata is updated; if false, chunks outside the new
462+ * bounds are deleted and boundary chunks are trimmed
463+ * @throws ZarrException if the new metadata is invalid
464+ * @throws IOException throws IOException if the new metadata cannot be serialized
465+ */
466+ public Array resize (long [] newShape , boolean resizeMetadataOnly ) throws ZarrException , IOException {
467+ return resize (newShape , resizeMetadataOnly , DEFAULT_PARALLELISM );
468+ }
469+
470+ /**
471+ * Sets a new shape for the Zarr array. This method returns a new instance of the Zarr array class
472+ * and the old instance becomes invalid.
473+ *
474+ * @param newShape the new shape of the Zarr array
475+ * @param resizeMetadataOnly if true, only the metadata is updated; if false, chunks outside the new
476+ * bounds are deleted and boundary chunks are trimmed
477+ * @param parallel utilizes parallelism if true when cleaning up chunks
478+ * @throws ZarrException if the new metadata is invalid
479+ * @throws IOException throws IOException if the new metadata cannot be serialized
480+ */
481+ public abstract Array resize (long [] newShape , boolean resizeMetadataOnly , boolean parallel ) throws ZarrException , IOException ;
482+
331483 public ArrayAccessor access () {
332484 return new ArrayAccessor (this );
333485 }
@@ -336,7 +488,7 @@ public static final class ArrayAccessor {
336488 @ Nullable
337489 long [] offset ;
338490 @ Nullable
339- int [] shape ;
491+ long [] shape ;
340492 @ Nonnull
341493 Array array ;
342494
@@ -353,13 +505,13 @@ public ArrayAccessor withOffset(@Nonnull long... offset) {
353505
354506 @ Nonnull
355507 public ArrayAccessor withShape (@ Nonnull int ... shape ) {
356- this .shape = shape ;
508+ this .shape = Utils . toLongArray ( shape ) ;
357509 return this ;
358510 }
359511
360512 @ Nonnull
361513 public ArrayAccessor withShape (@ Nonnull long ... shape ) {
362- this .shape = Utils . toIntArray ( shape ) ;
514+ this .shape = shape ;
363515 return this ;
364516 }
365517
0 commit comments