3939import java .util .Map ;
4040import java .util .concurrent .ExecutionException ;
4141import java .util .concurrent .ExecutorService ;
42+ import org .janelia .saalfeldlab .n5 .shard .Nesting .NestedGrid ;
43+ import org .janelia .saalfeldlab .n5 .shard .Nesting .NestedPosition ;
44+ import org .janelia .saalfeldlab .n5 .shard .Region ;
4245
4346/**
4447 * A simple structured container API for hierarchies of chunked
@@ -298,13 +301,33 @@ interface DataBlockSupplier<T> {
298301 * @param writeFully if false, merge existing data in shards/blocks that overlap the region boundary. if true, override everything.
299302 * @throws N5Exception the exception
300303 */
301- <T > void writeRegion (
304+ default <T > void writeRegion (
302305 String datasetPath ,
303306 DatasetAttributes datasetAttributes ,
304307 long [] min ,
305308 long [] size ,
306309 DataBlockSupplier <T > dataBlocks ,
307- boolean writeFully ) throws N5Exception ;
310+ boolean writeFully ) throws N5Exception {
311+
312+ final NestedGrid grid = datasetAttributes .getNestedBlockGrid ();
313+ final Region region = new Region (min , size , grid );
314+ for (long [] key : Region .gridPositions (region .minPos ().key (), region .maxPos ().key ())) {
315+ final NestedPosition pos = grid .nestedPosition (key , grid .numLevels () - 1 );
316+ final long [] gridPosition = pos .absolute (0 );
317+ final DataBlock <T > existingDataBlock = writeFully || region .fullyContains (pos )
318+ ? null
319+ : readBlock (datasetPath , datasetAttributes , gridPosition );
320+ final DataBlock <T > dataBlock = dataBlocks .get (gridPosition , existingDataBlock );
321+ // null blocks may be provided when they contain only the fill value
322+ // and only non-empty blocks should be written, for example
323+ if (dataBlock == null ) {
324+ deleteBlock (datasetPath , datasetAttributes , gridPosition );
325+ } else {
326+ writeBlock (datasetPath , datasetAttributes , dataBlock );
327+ }
328+ }
329+
330+ }
308331
309332 /**
310333 * @param datasetPath the dataset path
@@ -316,14 +339,35 @@ <T> void writeRegion(
316339 * @param exec used to parallelize over blocks and shards
317340 * @throws N5Exception the exception
318341 */
319- <T > void writeRegion (
342+ default <T > void writeRegion (
320343 String datasetPath ,
321344 DatasetAttributes datasetAttributes ,
322345 long [] min ,
323346 long [] size ,
324347 DataBlockSupplier <T > dataBlocks ,
325348 boolean writeFully ,
326- ExecutorService exec ) throws N5Exception , InterruptedException , ExecutionException ;
349+ ExecutorService exec ) throws N5Exception , InterruptedException , ExecutionException {
350+
351+ final NestedGrid grid = datasetAttributes .getNestedBlockGrid ();
352+ final Region region = new Region (min , size , grid );
353+ for (long [] key : Region .gridPositions (region .minPos ().key (), region .maxPos ().key ())) {
354+ exec .submit (() -> {
355+ final NestedPosition pos = grid .nestedPosition (key , grid .numLevels () - 1 );
356+ final long [] gridPosition = pos .absolute (0 );
357+ final DataBlock <T > existingDataBlock = writeFully || region .fullyContains (pos )
358+ ? null
359+ : readBlock (datasetPath , datasetAttributes , gridPosition );
360+ final DataBlock <T > dataBlock = dataBlocks .get (gridPosition , existingDataBlock );
361+ // null blocks may be provided when they contain only the fill value
362+ // and only non-empty blocks should be written, for example
363+ if (dataBlock == null ) {
364+ deleteBlock (datasetPath , datasetAttributes , gridPosition );
365+ } else {
366+ writeBlock (datasetPath , datasetAttributes , dataBlock );
367+ }
368+ });
369+ }
370+ }
327371
328372 /**
329373 * Deletes the block at {@code gridPosition}.
@@ -334,9 +378,27 @@ <T> void writeRegion(
334378 *
335379 * @return {@code true} if the block at {@code gridPosition} existed and was deleted.
336380 */
337- boolean deleteBlock (
381+ default boolean deleteBlock (
338382 final String datasetPath ,
339- final long ... gridPosition ) throws N5Exception ;
383+ final long ... gridPosition ) throws N5Exception {
384+ final DatasetAttributes datasetAttributes = getDatasetAttributes (datasetPath );
385+ return deleteBlock (datasetPath , datasetAttributes , gridPosition );
386+ }
387+
388+ /**
389+ * Deletes the block at {@code gridPosition}.
390+ *
391+ * @param datasetPath the dataset path
392+ * @param datasetAttributes the dataset attributes
393+ * @param gridPosition position of block to be deleted
394+ * @throws N5Exception if the block exists but could not be deleted
395+ *
396+ * @return {@code true} if the block at {@code gridPosition} existed and was deleted.
397+ */
398+ boolean deleteBlock (
399+ String datasetPath ,
400+ DatasetAttributes datasetAttributes ,
401+ long ... gridPosition ) throws N5Exception ;
340402
341403 /**
342404 * Deletes the blocks at the given {@code gridPositions}.
@@ -347,11 +409,12 @@ boolean deleteBlock(
347409 * @throws N5Exception if any of the block exists but could not be deleted
348410 */
349411 default boolean deleteBlocks (
350- final String datasetPath ,
351- final List <long []> gridPositions ) throws N5Exception {
412+ String datasetPath ,
413+ DatasetAttributes datasetAttributes ,
414+ List <long []> gridPositions ) throws N5Exception {
352415 boolean deleted = false ;
353416 for (long [] pos : gridPositions ) {
354- deleted |= deleteBlock (datasetPath , pos );
417+ deleted |= deleteBlock (datasetPath , datasetAttributes , pos );
355418 }
356419 return deleted ;
357420 }
0 commit comments