View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  
21  package org.waarp.compress;
22  
23  import com.github.luben.zstd.Zstd;
24  import com.github.luben.zstd.ZstdInputStream;
25  import com.github.luben.zstd.ZstdOutputStream;
26  import com.github.luben.zstd.util.Native;
27  import com.google.common.io.ByteStreams;
28  import org.waarp.common.file.FileUtils;
29  import org.waarp.common.logging.WaarpLogger;
30  import org.waarp.common.logging.WaarpLoggerFactory;
31  import org.waarp.compress.zstdjni.ZstdJniCodec;
32  import org.waarp.compress.zstdsafe.ZstdSafeCodec;
33  import org.waarp.compress.zstdunsafe.UnsafeUtil;
34  import org.waarp.compress.zstdunsafe.ZstdUnsafeCodec;
35  
36  import java.io.File;
37  import java.io.FileInputStream;
38  import java.io.FileOutputStream;
39  import java.io.InputStream;
40  import java.io.OutputStream;
41  
42  /**
43   * Waarp Compression Codec using byte array.<br>
44   * This will try to use one of the three implementations, according to
45   * environment, in the following order:<br>
46   * <ul>
47   *   <li>JNI based: the fastest, best compression ratio and less CPU</li>
48   *   <li>Unsafe based: 2 times slower than JNI but almost same in ratio and a
49   *   bit more CPU</li>
50   *   <li>Safe based: 4 times slower than JNI but almost same in ratio and a
51   *   bit more CPU</li>
52   * </ul>
53   */
54  public class WaarpZstdCodec {
55    private static final WaarpLogger logger =
56        WaarpLoggerFactory.getLogger(WaarpZstdCodec.class);
57    private final CompressorCodec codec;
58  
59    public WaarpZstdCodec() {
60      boolean jniLoaded;
61      try {
62        Native.load();
63        jniLoaded = true;
64      } catch (final Exception e) {
65        jniLoaded = false;
66        logger.warn("Cannot load JNI based ZSTD codec: {}", e.getMessage());
67      }
68      if (jniLoaded) {
69        codec = new ZstdJniCodec();
70      } else {
71        if (UnsafeUtil.isValid()) {
72          codec = new ZstdUnsafeCodec();
73        } else {
74          codec = new ZstdSafeCodec();
75        }
76      }
77    }
78  
79    /**
80     * @return the current Compressor Codec implementation
81     */
82    public final CompressorCodec getCompressorCodec() {
83      return codec;
84    }
85  
86    /**
87     * @param bufferSize
88     *
89     * @return the maximum size of a compressed buffer of size given
90     */
91    public static int getMaxCompressedSize(final int bufferSize) {
92      return ZstdSafeCodec.maxCompressedSize(bufferSize);
93    }
94  
95    /**
96     * Compress the file input to the file output
97     *
98     * @param input
99     * @param output
100    *
101    * @return the new length
102    *
103    * @throws MalformedInputException
104    */
105   public final long compress(final File input, final File output)
106       throws MalformedInputException {
107     InputStream inputStream = null;
108     OutputStream outputStream = null;
109     try {
110       final byte[] buffer;
111       if (codec instanceof ZstdJniCodec) {
112         inputStream = new FileInputStream(input);
113         outputStream = new ZstdOutputStream(new FileOutputStream(output), 1);
114         buffer = new byte[Zstd.blockSizeMax()];
115         while (true) {
116           final int r = inputStream.read(buffer);
117           if (r == -1) {
118             break;
119           }
120           outputStream.write(buffer, 0, r);
121         }
122       } else {
123         inputStream = new FileInputStream(input);
124         buffer = ByteStreams.toByteArray(inputStream);
125         outputStream = new FileOutputStream(output);
126         // Need to store in front the various position of block
127         final byte[] bufferCompression =
128             new byte[getMaxCompressedSize(buffer.length)];
129         final int length =
130             codec.compress(buffer, buffer.length, bufferCompression,
131                            bufferCompression.length);
132         outputStream.write(bufferCompression, 0, length);
133       }
134       outputStream.flush();
135       FileUtils.close(outputStream);
136       outputStream = null;
137       return output.length();
138     } catch (final Exception e) {
139       throw new MalformedInputException(e);
140     } finally {
141       FileUtils.close(inputStream);
142       FileUtils.close(outputStream);
143     }
144   }
145 
146   /**
147    * Decompress the file input to the file output
148    *
149    * @param input
150    * @param output
151    *
152    * @return the new length
153    *
154    * @throws MalformedInputException
155    */
156   public final long decompress(final File input, final File output)
157       throws MalformedInputException {
158     InputStream inputStream = null;
159     OutputStream outputStream = null;
160     try {
161       final byte[] buffer;
162       if (codec instanceof ZstdJniCodec) {
163         inputStream = new ZstdInputStream(new FileInputStream(input));
164         outputStream = new FileOutputStream(output);
165         buffer = new byte[Zstd.blockSizeMax()];
166         while (true) {
167           final int r = inputStream.read(buffer);
168           if (r == -1) {
169             break;
170           }
171           outputStream.write(buffer, 0, r);
172         }
173       } else {
174         inputStream = new FileInputStream(input);
175         final byte[] sourceArray = ByteStreams.toByteArray(inputStream);
176         outputStream = new FileOutputStream(output);
177         // Need to store in front the various position of block
178         buffer = new byte[codec.getDecompressedSize(sourceArray,
179                                                     sourceArray.length)];
180         final int length =
181             codec.decompress(sourceArray, sourceArray.length, buffer,
182                              buffer.length);
183         outputStream.write(buffer, 0, length);
184       }
185       outputStream.flush();
186       FileUtils.close(outputStream);
187       outputStream = null;
188       return output.length();
189     } catch (final Exception e) {
190       throw new MalformedInputException(e);
191     } finally {
192       FileUtils.close(inputStream);
193       FileUtils.close(outputStream);
194     }
195   }
196 }