diff --git a/src/main/java/dev/zarr/zarrjava/core/codec/core/GzipCodec.java b/src/main/java/dev/zarr/zarrjava/core/codec/core/GzipCodec.java new file mode 100644 index 00000000..42f687a5 --- /dev/null +++ b/src/main/java/dev/zarr/zarrjava/core/codec/core/GzipCodec.java @@ -0,0 +1,42 @@ +package dev.zarr.zarrjava.core.codec.core; + +import dev.zarr.zarrjava.ZarrException; +import dev.zarr.zarrjava.core.codec.BytesBytesCodec; +import dev.zarr.zarrjava.utils.Utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public abstract class GzipCodec extends BytesBytesCodec { + + @Override + public ByteBuffer decode(ByteBuffer chunkBytes) throws ZarrException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); GZIPInputStream inputStream = new GZIPInputStream( + new ByteArrayInputStream(Utils.toArray(chunkBytes)))) { + Utils.copyStream(inputStream, outputStream); + inputStream.close(); + return ByteBuffer.wrap(outputStream.toByteArray()); + } catch (IOException ex) { + throw new ZarrException("Error in decoding gzip.", ex); + } + } + + protected ByteBuffer encodeInternal(int level, ByteBuffer chunkBytes) throws ZarrException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); GZIPOutputStream gzipStream = new GZIPOutputStream( + outputStream) { + { + this.def.setLevel(level); + } + }) { + gzipStream.write(Utils.toArray(chunkBytes)); + gzipStream.close(); + return ByteBuffer.wrap(outputStream.toByteArray()); + } catch (IOException ex) { + throw new ZarrException("Error in encoding gzip.", ex); + } + } +} diff --git a/src/main/java/dev/zarr/zarrjava/v2/ArrayMetadataBuilder.java b/src/main/java/dev/zarr/zarrjava/v2/ArrayMetadataBuilder.java index 00c37687..9b87addb 100644 --- a/src/main/java/dev/zarr/zarrjava/v2/ArrayMetadataBuilder.java +++ b/src/main/java/dev/zarr/zarrjava/v2/ArrayMetadataBuilder.java @@ -7,6 +7,7 @@ import dev.zarr.zarrjava.utils.Utils; import dev.zarr.zarrjava.v2.codec.Codec; import dev.zarr.zarrjava.v2.codec.core.BloscCodec; +import dev.zarr.zarrjava.v2.codec.core.GzipCodec; import dev.zarr.zarrjava.v2.codec.core.ZlibCodec; import dev.zarr.zarrjava.v2.codec.core.ZstdCodec; @@ -114,6 +115,19 @@ public ArrayMetadataBuilder withBloscCompressor() { return withBloscCompressor("zstd"); } + public ArrayMetadataBuilder withGzipCompressor(int level) { + try { + this.compressor = new GzipCodec(level); + } catch (ZarrException e) { + throw new RuntimeException(e); + } + return this; + } + + public ArrayMetadataBuilder withGzipCompressor() { + return withGzipCompressor(GzipCodec.DEFAULT_LEVEL); + } + public ArrayMetadataBuilder withZlibCompressor(int level) { try { this.compressor = new ZlibCodec(level); diff --git a/src/main/java/dev/zarr/zarrjava/v2/codec/CodecRegistry.java b/src/main/java/dev/zarr/zarrjava/v2/codec/CodecRegistry.java index f0cb7fab..46d9c83a 100644 --- a/src/main/java/dev/zarr/zarrjava/v2/codec/CodecRegistry.java +++ b/src/main/java/dev/zarr/zarrjava/v2/codec/CodecRegistry.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.jsontype.NamedType; import dev.zarr.zarrjava.v2.codec.core.BloscCodec; +import dev.zarr.zarrjava.v2.codec.core.GzipCodec; import dev.zarr.zarrjava.v2.codec.core.ZlibCodec; import dev.zarr.zarrjava.v2.codec.core.ZstdCodec; @@ -14,6 +15,7 @@ public class CodecRegistry { static { addType("blosc", BloscCodec.class); + addType("gzip", GzipCodec.class); addType("zlib", ZlibCodec.class); addType("zstd", ZstdCodec.class); } diff --git a/src/main/java/dev/zarr/zarrjava/v2/codec/core/GzipCodec.java b/src/main/java/dev/zarr/zarrjava/v2/codec/core/GzipCodec.java new file mode 100644 index 00000000..42e8f505 --- /dev/null +++ b/src/main/java/dev/zarr/zarrjava/v2/codec/core/GzipCodec.java @@ -0,0 +1,38 @@ +package dev.zarr.zarrjava.v2.codec.core; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import dev.zarr.zarrjava.ZarrException; +import dev.zarr.zarrjava.core.ArrayMetadata; +import dev.zarr.zarrjava.v2.codec.Codec; + +import java.nio.ByteBuffer; + +public class GzipCodec extends dev.zarr.zarrjava.core.codec.core.GzipCodec implements Codec { + + public static final int DEFAULT_LEVEL = 1; + @JsonIgnore + public final String id = "gzip"; + public final int level; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GzipCodec( + @JsonProperty(value = "level", defaultValue = "" + DEFAULT_LEVEL) int level) + throws ZarrException { + if (level < 0 || level > 9) { + throw new ZarrException("'level' needs to be between 0 and 9."); + } + this.level = level; + } + + @Override + public ByteBuffer encode(ByteBuffer chunkBytes) throws ZarrException { + return encodeInternal(this.level, chunkBytes); + } + + @Override + public Codec evolveFromCoreArrayMetadata(ArrayMetadata.CoreArrayMetadata arrayMetadata) { + return this; + } +} diff --git a/src/main/java/dev/zarr/zarrjava/v3/codec/core/GzipCodec.java b/src/main/java/dev/zarr/zarrjava/v3/codec/core/GzipCodec.java index 80ef9d21..910d75b2 100644 --- a/src/main/java/dev/zarr/zarrjava/v3/codec/core/GzipCodec.java +++ b/src/main/java/dev/zarr/zarrjava/v3/codec/core/GzipCodec.java @@ -4,20 +4,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import dev.zarr.zarrjava.ZarrException; -import dev.zarr.zarrjava.core.codec.BytesBytesCodec; -import dev.zarr.zarrjava.utils.Utils; import dev.zarr.zarrjava.v3.ArrayMetadata; import dev.zarr.zarrjava.v3.codec.Codec; import javax.annotation.Nonnull; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.nio.ByteBuffer; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; -public class GzipCodec extends BytesBytesCodec implements Codec { +public class GzipCodec extends dev.zarr.zarrjava.core.codec.core.GzipCodec implements Codec { @JsonIgnore public final String name = "gzip"; @@ -30,31 +23,9 @@ public GzipCodec( this.configuration = configuration; } - - @Override - public ByteBuffer decode(ByteBuffer chunkBytes) - throws ZarrException { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); GZIPInputStream inputStream = new GZIPInputStream( - new ByteArrayInputStream(Utils.toArray(chunkBytes)))) { - Utils.copyStream(inputStream, outputStream); - inputStream.close(); - return ByteBuffer.wrap(outputStream.toByteArray()); - } catch (IOException ex) { - throw new ZarrException("Error in decoding gzip.", ex); - } - } - @Override - public ByteBuffer encode(ByteBuffer chunkBytes) - throws ZarrException { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); GZIPOutputStream gzipStream = new GZIPOutputStream( - outputStream)) { - gzipStream.write(Utils.toArray(chunkBytes)); - gzipStream.close(); - return ByteBuffer.wrap(outputStream.toByteArray()); - } catch (IOException ex) { - throw new ZarrException("Error in encoding gzip.", ex); - } + public ByteBuffer encode(ByteBuffer chunkBytes) throws ZarrException { + return encodeInternal(configuration.level, chunkBytes); } @Override @@ -77,5 +48,3 @@ public Configuration(@JsonProperty(value = "level", defaultValue = "5") int leve } } } - - diff --git a/src/test/java/dev/zarr/zarrjava/ZarrPythonTests.java b/src/test/java/dev/zarr/zarrjava/ZarrPythonTests.java index 5c22b5fc..9bf0eebf 100644 --- a/src/test/java/dev/zarr/zarrjava/ZarrPythonTests.java +++ b/src/test/java/dev/zarr/zarrjava/ZarrPythonTests.java @@ -107,6 +107,8 @@ static Stream compressorAndDataTypeProviderV2() { new Object[]{"blosc", "lz4hc_bitshuffle_3", dev.zarr.zarrjava.v2.DataType.INT32}, new Object[]{"blosc", "zlib_shuffle_5", dev.zarr.zarrjava.v2.DataType.INT32}, new Object[]{"blosc", "zstd_bitshuffle_9", dev.zarr.zarrjava.v2.DataType.INT32}, + new Object[]{"gzip", "0", dev.zarr.zarrjava.v2.DataType.INT32}, + new Object[]{"gzip", "5", dev.zarr.zarrjava.v2.DataType.INT32}, new Object[]{"zstd", "0_true", dev.zarr.zarrjava.v2.DataType.INT32}, new Object[]{"zstd", "5_false", dev.zarr.zarrjava.v2.DataType.INT32} ); @@ -241,6 +243,9 @@ public void testWriteV2(String compressor, String compressorParam, dev.zarr.zarr int clevel_blosc = Integer.parseInt(compressorParam.split("_")[2]); builder = builder.withBloscCompressor(cname, shuffle, clevel_blosc); break; + case "gzip": + builder = builder.withGzipCompressor(Integer.parseInt(compressorParam)); + break; case "zlib": builder = builder.withZlibCompressor(Integer.parseInt(compressorParam)); break; diff --git a/src/test/python-scripts/parse_codecs.py b/src/test/python-scripts/parse_codecs.py index b3502660..f28b2779 100644 --- a/src/test/python-scripts/parse_codecs.py +++ b/src/test/python-scripts/parse_codecs.py @@ -30,6 +30,8 @@ def parse_codecs_zarr_python(codec_string: str, param_string: str, zarr_version: compressor = numcodecs.Blosc(typesize=4, cname=cname, shuffle=shuffle, clevel=int(clevel)) elif codec_string == "zlib" and zarr_version == 2: compressor = numcodecs.Zlib(level=int(param_string)) + elif codec_string == "gzip" and zarr_version == 2: + compressor = numcodecs.GZip(level=int(param_string)) elif codec_string == "gzip" and zarr_version == 3: compressor = GzipCodec(level=int(param_string)) elif codec_string == "zstd" and zarr_version == 3: