3535import org .janelia .saalfeldlab .n5 .FileSystemKeyValueAccess ;
3636import org .janelia .saalfeldlab .n5 .KeyValueAccess ;
3737import org .janelia .saalfeldlab .n5 .N5Exception ;
38+ import org .janelia .saalfeldlab .n5 .N5Exception .N5IOException ;
3839import org .janelia .saalfeldlab .n5 .N5KeyValueReader ;
3940import org .janelia .saalfeldlab .n5 .N5KeyValueWriter ;
4041import org .janelia .saalfeldlab .n5 .N5Reader ;
5859import java .net .URISyntaxException ;
5960import java .nio .file .Paths ;
6061import java .util .Arrays ;
62+ import java .util .List ;
6163import java .util .function .BiFunction ;
6264import java .util .function .Consumer ;
6365import java .util .regex .Pattern ;
6466
65- import static org .janelia .saalfeldlab .n5 .universe .StorageFormat .getStorageFromNestedScheme ;
66- import static org .janelia .saalfeldlab .n5 .universe .StorageFormat .guessStorageFromKeys ;
67+ import static org .janelia .saalfeldlab .n5 .universe .StorageFormat .*;
6768
6869/**
6970 * Factory for various N5 readers and writers. Implementation-specific
@@ -244,7 +245,7 @@ public N5Reader openFSReader(final String path) {
244245 @ Deprecated
245246 public N5Reader openZarrReader (final String path ) {
246247
247- return openN5ContainerWithStorageFormat (StorageFormat .ZARR2 , path , this ::openReader );
248+ return openN5ContainerWithStorageFormat (StorageFormat .ZARR , path , this ::openReader );
248249 }
249250
250251 /**
@@ -274,7 +275,10 @@ public N5Reader openHDF5Reader(final String path) {
274275 * @param uri uri to the google cloud object
275276 * @return the N5Reader
276277 * @throws URISyntaxException if uri is malformed
278+ *
279+ * @deprecated use {@link N5Factory#openReader(KeyValueAccessBackend, URI)} instead
277280 */
281+ @ Deprecated
278282 public N5Reader openGoogleCloudReader (final String uri ) throws URISyntaxException {
279283
280284 return openN5ContainerWithBackend (KeyValueAccessBackend .GOOGLE_CLOUD , uri , true , this ::openReader );
@@ -286,7 +290,10 @@ public N5Reader openGoogleCloudReader(final String uri) throws URISyntaxExceptio
286290 * @param uri uri to the amazon s3 object
287291 * @return the N5Reader
288292 * @throws URISyntaxException if uri is malformed
293+ *
294+ * @deprecated use {@link N5Factory#openReader(KeyValueAccessBackend, URI)} instead
289295 */
296+ @ Deprecated
290297 public N5Reader openAWSS3Reader (final String uri ) throws URISyntaxException {
291298
292299 return openN5ContainerWithBackend (KeyValueAccessBackend .AWS , uri , true , this ::openReader );
@@ -298,12 +305,23 @@ public N5Reader openAWSS3Reader(final String uri) throws URISyntaxException {
298305 * @param uri uri to the N5Reader
299306 * @return the N5Reader
300307 * @throws URISyntaxException if uri is malformed
308+ *
309+ * @deprecated use {@link N5Factory#openReader(KeyValueAccessBackend, URI)} instead
301310 */
311+ @ Deprecated
302312 public N5Reader openFileSystemReader (final String uri ) throws URISyntaxException {
303313
304314 return openN5ContainerWithBackend (KeyValueAccessBackend .FILE , uri , true , this ::openReader );
305315 }
306316
317+ StorageFormat [] orderedStorageFormats () {
318+
319+ return Arrays .stream (StorageFormat .values ()).sorted ((l , r ) -> {
320+ if (l == preferredStorageFormat ) return -1 ;
321+ return l .compareTo (r );
322+ }).toArray (StorageFormat []::new );
323+ }
324+
307325 /**
308326 * Open an {@link N5Reader} at the given URL, with best attempts to infer
309327 * the appropriate {@link KeyValueAccessBackend} and {@link StorageFormat}.
@@ -353,14 +371,39 @@ public N5Reader openReader(final StorageFormat format, final URI uri) {
353371 return openN5Container (format , uri , true , this ::openReader );
354372 }
355373
356- StorageFormat [] orderedStorageFormats () {
357-
358- return Arrays .stream (StorageFormat .values ()).sorted ((l , r ) -> {
359- if (l == preferredStorageFormat ) return -1 ;
360- return l .compareTo (r );
361- }).toArray (StorageFormat []::new );
374+ /**
375+ * Open and N5Reader at the given URI given the specified {@code KeyValueAccessBackend}.
376+ *
377+ * @param backend key-value access
378+ * @param uri location of reader
379+ * @return the N5Reader
380+ */
381+ public N5Reader openReader (final KeyValueAccessBackend backend , final String uri ) {
382+ final Pair <StorageFormat , URI > storageAndUri = StorageFormat .parseUri (uri );
383+ final StorageFormat format = storageAndUri .getA ();
384+ final URI asUri = storageAndUri .getB ();
385+ final boolean inferredStorageFormat = format != null && getStorageFromNestedScheme (uri ).getA () == null ;
386+ final KeyValueAccess kva = backend .apply (asUri , this , true );
387+ if (inferredStorageFormat ) {
388+ final StorageFormat inferredFromKeys = guessStorageFromKeys (asUri , kva );
389+ final StorageFormat inferredFormat = inferredFromKeys != null ? inferredFromKeys : format ;
390+ return openReader (inferredFormat , kva , asUri );
391+ }
392+ return openReader (format , kva , asUri );
362393 }
363394
395+ /**
396+ * Open and N5Reader at the given URI given the specified {@code KeyValueAccessBackend}.
397+ *
398+ * @param backend key-value access
399+ * @param uri location of reader
400+ * @return the N5Reader
401+ */
402+ public N5Reader openReader (final KeyValueAccessBackend backend , final URI uri ) {
403+ final KeyValueAccess kva = backend .apply (uri , this , true );
404+ final StorageFormat inferredFromKeys = guessStorageFromKeys (uri , kva );
405+ return openReader (inferredFromKeys , kva , uri );
406+ }
364407
365408
366409 /**
@@ -371,8 +414,7 @@ StorageFormat[] orderedStorageFormats() {
371414 * @param location root URI of the resulting N5Reader
372415 * @return the N5Reader
373416 */
374- private N5Reader openReader (@ Nullable final StorageFormat storage , @ Nullable final KeyValueAccess access , URI location ) {
375-
417+ public N5Reader openReader (@ Nullable final StorageFormat storage , final KeyValueAccess access , URI location ) {
376418
377419 if (storage == null ) {
378420 for (final StorageFormat format : orderedStorageFormats ()) {
@@ -389,17 +431,37 @@ private N5Reader openReader(@Nullable final StorageFormat storage, @Nullable fin
389431 switch (storage ) {
390432 case N5 :
391433 return new N5KeyValueReader (access , containerPath , gsonBuilder , cacheAttributes );
392- case ZARR :
434+ case ZARR3 :
393435 return new ZarrV3KeyValueReader (access ,containerPath , gsonBuilder , cacheAttributes );
394436 case ZARR2 :
395437 return new ZarrKeyValueReader (access , containerPath , gsonBuilder , zarrMapN5DatasetAttributes , zarrMergeAttributes , cacheAttributes );
438+ case ZARR :
439+ return newGenericZarrReader (access , location );
396440 case HDF5 :
397441 return new N5HDF5Reader (containerPath , hdf5OverrideBlockSize , gsonBuilder , hdf5DefaultBlockSize );
398442 }
399443 return null ;
400444 }
401445 }
402446
447+ /**
448+ * Open a zarr as N5Reader at the given {@code access} and {@code location}.
449+ * Will prefer returning the newest version of zarr that is found at the location.
450+ *
451+ * @param access to the key-value access backend
452+ * @param location of the zarr container
453+ * @return an N5Reader
454+ */
455+ private N5Reader newGenericZarrReader (final KeyValueAccess access , final URI location ) {
456+ for (StorageFormat zarrFormat : Arrays .asList (ZARR3 , ZARR2 )) {
457+ try {
458+ return openReader (zarrFormat , access , location );
459+ } catch (Exception ignored ) {
460+ }
461+ }
462+ throw new N5IOException ("Unable to open Zarr reader at " + location .toString () + " as N5Reader" );
463+ }
464+
403465 /**
404466 * Open an {@link N5Writer} for N5 Container.
405467 * <p>
@@ -432,7 +494,7 @@ public N5Writer openFSWriter(final String path) {
432494 @ Deprecated
433495 public N5Writer openZarrWriter (final String path ) {
434496
435- return openN5ContainerWithStorageFormat (StorageFormat .ZARR2 , path , this ::openWriter );
497+ return openN5ContainerWithStorageFormat (StorageFormat .ZARR , path , this ::openWriter );
436498 }
437499
438500 /**
@@ -462,7 +524,10 @@ public N5Writer openHDF5Writer(final String path) {
462524 * @param uri uri to the google cloud object
463525 * @return the N5GoogleCloudStorageWriter
464526 * @throws URISyntaxException if uri is malformed
527+ *
528+ * @deprecated use {@link #openWriter(KeyValueAccessBackend, String)} instead.
465529 */
530+ @ Deprecated
466531 public N5Writer openGoogleCloudWriter (final String uri ) throws URISyntaxException {
467532
468533 return openN5ContainerWithBackend (KeyValueAccessBackend .GOOGLE_CLOUD , uri , false , this ::openWriter );
@@ -474,7 +539,11 @@ public N5Writer openGoogleCloudWriter(final String uri) throws URISyntaxExceptio
474539 * @param uri uri to the s3 object
475540 * @return the N5Writer
476541 * @throws URISyntaxException if the URI is malformed
542+ *
543+ *
544+ * @deprecated use {@link #openWriter(KeyValueAccessBackend, String)} instead.
477545 */
546+ @ Deprecated ()
478547 public N5Writer openAWSS3Writer (final String uri ) throws URISyntaxException {
479548
480549 return openN5ContainerWithBackend (KeyValueAccessBackend .AWS , uri , false , this ::openWriter );
@@ -496,12 +565,47 @@ public N5Writer openWriter(final String uri) {
496565 final KeyValueAccess kva = getKeyValueAccess (asUri , false );
497566 final StorageFormat inferredFromKeys = guessStorageFromKeys (asUri , kva );
498567 final StorageFormat inferredFormat = inferredFromKeys != null ? inferredFromKeys : format ;
499- return openWriter (inferredFormat , asUri );
568+ return openWriter (inferredFormat , kva , asUri );
500569 }
501570
502571 return openWriter (format , asUri );
503572 }
504573
574+ /**
575+ * Create or Open an N5Writer as the given uri and backend.
576+ *
577+ * @param backend key-value access
578+ * @param uri location of writer
579+ * @return the N5Writer
580+ */
581+ public N5Writer openWriter (final KeyValueAccessBackend backend , final URI uri ) {
582+ final KeyValueAccess kva = backend .apply (uri , this , false );
583+ final StorageFormat inferredFromKeys = guessStorageFromKeys (uri , kva );
584+ return openWriter (inferredFromKeys , kva , uri );
585+ }
586+
587+ /**
588+ * Create or Open an N5Writer as the given uri and backend.
589+ *
590+ * @param backend key-value access
591+ * @param uri location of writer
592+ * @return the N5Writer
593+ */
594+ public N5Writer openWriter (final KeyValueAccessBackend backend , final String uri ) {
595+ final Pair <StorageFormat , URI > storageAndUri = StorageFormat .parseUri (uri );
596+ final StorageFormat format = storageAndUri .getA ();
597+ final URI asUri = storageAndUri .getB ();
598+ final KeyValueAccess kva = backend .apply (asUri , this , false );
599+ final boolean inferredStorageFormat = format != null && getStorageFromNestedScheme (uri ).getA () == null ;
600+ if (inferredStorageFormat ) {
601+ final StorageFormat inferredFromKeys = guessStorageFromKeys (asUri , kva );
602+ final StorageFormat inferredFormat = inferredFromKeys != null ? inferredFromKeys : format ;
603+ return openWriter (inferredFormat , kva , asUri );
604+ }
605+
606+ return openWriter (format , kva , asUri );
607+ }
608+
505609 /**
506610 * Open an {@link N5Writer} at the given URL, with best attempts to infer
507611 * the appropriate {@link KeyValueAccessBackend}.
@@ -538,7 +642,7 @@ public N5Writer openWriter(final StorageFormat format, final URI uri) {
538642 * @param location root location of the resulting N5Writer
539643 * @return the N5Writer
540644 */
541- public N5Writer openWriter (@ Nullable final StorageFormat storage , @ Nullable final KeyValueAccess access , final URI location ) {
645+ public N5Writer openWriter (@ Nullable final StorageFormat storage , final KeyValueAccess access , final URI location ) {
542646
543647 if (storage == null ) {
544648 for (final StorageFormat format : orderedStorageFormats ()) {
@@ -553,12 +657,14 @@ public N5Writer openWriter(@Nullable final StorageFormat storage, @Nullable fina
553657
554658 final String containerLocation = location .toString ();
555659 switch (storage ) {
556- case ZARR :
660+ case ZARR3 :
557661 final ZarrV3KeyValueWriter writer = new ZarrV3KeyValueWriter (access , containerLocation , gsonBuilder , cacheAttributes );
558662 writer .setDimensionSeparator (zarrDimensionSeparator );
559663 return writer ;
560664 case ZARR2 :
561665 return new ZarrKeyValueWriter (access , containerLocation , gsonBuilder , zarrMapN5DatasetAttributes , zarrMergeAttributes , zarrDimensionSeparator , cacheAttributes );
666+ case ZARR :
667+ return newGenericZarrWriter (access , location );
562668 case N5 :
563669 return new N5KeyValueWriter (access , containerLocation , gsonBuilder , cacheAttributes );
564670 case HDF5 :
@@ -568,6 +674,37 @@ public N5Writer openWriter(@Nullable final StorageFormat storage, @Nullable fina
568674 return null ;
569675 }
570676
677+ /**
678+ * Try to get a zarr writer at the given location and access.
679+ * If a container exists at the location, load that version as a writer if possible.
680+ * If no container exists, create a new writer with the newest zarr version.
681+ *
682+ * @param access to the key-value backend
683+ * @param location of the zarr writer
684+ * @return the zarr writer
685+ */
686+ private N5Writer newGenericZarrWriter (final KeyValueAccess access , final URI location ) {
687+ List <StorageFormat > zarrFormats = Arrays .asList (ZARR3 , ZARR2 );
688+ for (StorageFormat zarrFormat : zarrFormats ) {
689+ /* we dont care about the read, but we do want to prefer a writer over a container that
690+ * exists, rather than creating a new writer; the only way to check is to see if
691+ * we can get a valid reader. If we can, try the writer. */
692+ try (N5Reader ignore = openReader (zarrFormat , access , location )) {
693+ return openWriter (zarrFormat , access , location );
694+ } catch (Exception ignored ) {
695+ }
696+ }
697+ /* However, if we have no valid readers, then try and return the first valid (created) writer */
698+ for (StorageFormat zarrFormat : zarrFormats ) {
699+ try {
700+ return openWriter (zarrFormat , access , location );
701+ } catch (Exception ignored ) {
702+ }
703+ }
704+
705+ throw new N5IOException ("Unable to open Zarr writer at " + location .toString () + " as N5Reader" );
706+ }
707+
571708 private <T extends N5Reader > T openN5ContainerWithStorageFormat (
572709 final StorageFormat format ,
573710 final String uri ,
0 commit comments