33import dev .zarr .zarrjava .ZarrException ;
44import dev .zarr .zarrjava .core .codec .CodecPipeline ;
55import dev .zarr .zarrjava .store .FilesystemStore ;
6- import dev .zarr .zarrjava .store .Store ;
76import dev .zarr .zarrjava .store .StoreHandle ;
87import dev .zarr .zarrjava .utils .IndexingUtils ;
98import dev .zarr .zarrjava .utils .MultiArrayUtils ;
1716import java .nio .file .Path ;
1817import java .nio .file .Paths ;
1918import java .util .Arrays ;
20- import java .util .List ;
21- import java .util .Set ;
22- import java .util .stream .Collectors ;
2319import java .util .stream .Stream ;
2420
2521public abstract class Array extends AbstractNode {
2622
2723 protected CodecPipeline codecPipeline ;
24+ public static final boolean DEFAULT_PARALLELISM = true ;
2825
2926 protected Array (StoreHandle storeHandle ) throws ZarrException {
3027 super (storeHandle );
@@ -184,6 +181,110 @@ public ucar.ma2.Array readChunk(long[] chunkCoords) throws ZarrException {
184181 return codecPipeline .decode (chunkBytes );
185182 }
186183
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+
187288
188289 /**
189290 * Writes a ucar.ma2.Array into the Zarr array at the beginning of the Zarr array. The shape of
@@ -205,7 +306,7 @@ public void write(ucar.ma2.Array array) {
205306 * @param array the data to write
206307 */
207308 public void write (long [] offset , ucar .ma2 .Array array ) {
208- write (offset , array , false );
309+ write (offset , array , DEFAULT_PARALLELISM );
209310 }
210311
211312 /**
@@ -240,7 +341,7 @@ public ucar.ma2.Array read() throws ZarrException {
240341 */
241342 @ Nonnull
242343 public ucar .ma2 .Array read (final long [] offset , final long [] shape ) throws ZarrException {
243- return read (offset , shape , false );
344+ return read (offset , shape , DEFAULT_PARALLELISM );
244345 }
245346
246347 /**
@@ -339,6 +440,46 @@ public ucar.ma2.Array read(final long[] offset, final long[] shape, final boolea
339440 return outputArray ;
340441 }
341442
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+
342483 public ArrayAccessor access () {
343484 return new ArrayAccessor (this );
344485 }
0 commit comments