All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jstuff.integration.compression.ZStdCompression Maven / Gradle / Ivy

/*
 * Copyright 2010-2022 by Sebastian Thomschke and contributors.
 * SPDX-License-Identifier: EPL-2.0
 */
package net.sf.jstuff.integration.compression;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdInputStream;
import com.github.luben.zstd.ZstdOutputStream;

import net.sf.jstuff.core.Strings;
import net.sf.jstuff.core.compression.AbstractCompression;
import net.sf.jstuff.core.validation.Args;

/**
 * https://gregoryszorc.com/blog/2017/03/07/better-compression-with-zstandard/
 * https://code.facebook.com/posts/1658392934479273/smaller-and-faster-data-compression-with-zstandard/
 * https://quixdb.github.io/squash-benchmark/
 *
 * @author Sebastian Thomschke
 */
public class ZStdCompression extends AbstractCompression {

   public static final int LEVEL_SMALL_AS_DEFLATE_4 = 2;
   public static final int LEVEL_SMALL_AS_DEFLATE_6 = 3;
   public static final int LEVEL_SMALL_AS_DEFLATE_9 = 3;

   public static final ZStdCompression INSTANCE = new ZStdCompression(LEVEL_SMALL_AS_DEFLATE_4, false);

   private final boolean useChecksum;
   private final int compressionLevel;

   public ZStdCompression(final int compressionLevel) {
      this(compressionLevel, false);
   }

   public ZStdCompression(final int compressionLevel, final boolean useChecksum) {
      this.compressionLevel = compressionLevel;
      this.useChecksum = useChecksum;
   }

   @Override
   public byte[] compress(final byte[] uncompressed) throws IOException {
      Args.notNull("uncompressed", uncompressed);

      final long maxSize = Zstd.compressBound(uncompressed.length);
      if (maxSize > Integer.MAX_VALUE)
         throw new IOException("Max output size is greater than Integer.MAX_VALUE!");
      final var dst = new byte[(int) maxSize];

      final long rc = Zstd.compress(dst, uncompressed, compressionLevel);
      if (Zstd.isError(rc))
         throw new IOException(Zstd.getErrorName(rc));

      final int size = (int) rc;
      final var out = new byte[size];
      System.arraycopy(dst, 0, out, 0, size);
      return out;
   }

   @Override
   @SuppressWarnings("resource")
   public OutputStream createCompressingOutputStream(final OutputStream output) throws IOException {
      Args.notNull("output", output);

      final var out = new ZstdOutputStream(output, compressionLevel);
      out.setCloseFrameOnFlush(true);
      out.setChecksum(useChecksum);
      return out;
   }

   @Override
   @SuppressWarnings("resource")
   public InputStream createDecompressingInputStream(final InputStream compressed) throws IOException {
      Args.notNull("compressed", compressed);

      return new ZstdInputStream(compressed);
   }

   @Override
   public int decompress(final byte[] compressed, final byte[] output) throws IOException {
      Args.notNull("compressed", compressed);
      Args.notNull("output", output);

      final long rc = Zstd.decompress(output, compressed);
      if (Zstd.isError(rc)) {
         if (rc == -70)
            throw new IndexOutOfBoundsException("[output] byte array of size " + output.length + " is too small for given input.");
         throw new IOException(Zstd.getErrorName(rc));
      }
      return (int) rc;
   }

   public int getCompressionLevel() {
      return compressionLevel;
   }

   public boolean isUseChecksum() {
      return useChecksum;
   }

   @Override
   public String toString() {
      return Strings.toString(this, "compressionLevel", compressionLevel);
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy