/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.util.zip; import java.io.ByteArrayOutputStream; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import totalcross.io.IOException; import totalcross.io.Stream; /** * This class implements stream compression with the ZLib library. * * <blockquote> <i><q>zlib is designed to be a free, general-purpose, legally unencumbered - that is, not covered by any * patents -- lossless data-compression library for use on virtually any computer hardware and operating system. The * zlib data format is itself portable across platforms.</q></i> * <p align=right> * <a href="http://www.zlib.net/" />http://www.zlib.net/</a> * </p> * </blockquote> */ final public class ZLib { /** Compression level for no compression, which is 0 in a scale from 0 (no compression) to 9 (best compression) */ public static final int NO_COMPRESSION = 0; /** Compression level for fastest compression, which is 1 in a scale from 0 (no compression) to 9 (best compression). */ public static final int BEST_SPEED = 1; /** Compression level for best compression, which is 9 in a scale from 0 (no compression) to 9 (best compression). */ public static final int BEST_COMPRESSION = 9; /** Default compression level. */ public static final int DEFAULT_COMPRESSION = -1; /** Default compression strategy. */ public static final int DEFAULT_STRATEGY = 0; /** Compression method for the deflate algorithm (the only one currently supported). */ public static final int DEFLATED = 8; /** Compression strategy best used for data consisting mostly of small values with a somewhat random distribution. */ public static final int FILTERED = 1; /** Compression strategy for Huffman coding only. */ public static final int HUFFMAN_ONLY = 2; /** * Deflates the given stream 'in' with the specified compression level, writing the result to the given stream 'out'. * Compressed data will be generated in ZLIB format using the default strategy and the default compression level. * * @param in * Stream to be deflated * @param out * Deflated stream. * @return Size of the deflated stream * @throws IOException * @see #DEFAULT_COMPRESSION */ public static int deflate(Stream in, Stream out) throws IOException { return deflate(in, out, DEFAULT_COMPRESSION, DEFAULT_STRATEGY, false); } /** * Deflates the given stream 'in' with the specified compression level, writing the result to the given stream 'out'. * Compressed data will be generated in ZLIB format using the default strategy. * * @param in * Stream to be deflated * @param out * Deflated stream. * @param compressionLevel * Desired compression level, which must be between 0 and 9, or -1 for the default compression level * @return Size of the deflated stream * @throws IOException * @see #NO_COMPRESSION * @see #BEST_SPEED * @see #BEST_COMPRESSION */ public static int deflate(Stream in, Stream out, int compressionLevel) throws IOException { return deflate(in, out, compressionLevel, DEFAULT_STRATEGY, false); } /** * Deflates the given stream 'in' with the specified compression level, writing the result to the given stream 'out'. * Compressed data will be generated in ZLIB format using the default strategy. * * @param compressionLevel * Desired compression level, which must be between 0 and 9, or -1 for the default compression level * @param in * Stream to be deflated * @param out * Deflated stream. * @return Size of the deflated stream * @throws IOException * @see #NO_COMPRESSION * @see #BEST_SPEED * @see #BEST_COMPRESSION * @deprecated use #deflate(Stream, Stream, int) instead */ public static int deflate(int compressionLevel, Stream in, Stream out) throws IOException { return deflate(in, out, compressionLevel, DEFAULT_STRATEGY, false); } /** * Deflates the given stream 'in' using the specified strategy and compression level, writing the result to the given * stream 'out'. If 'nowrap' is true then the ZLIB header and checksum fields will not be used in order to support * the compression format used in both GZIP and PKZIP. * * @param in * Stream to be deflated * @param out * Deflated stream. * @param compressionLevel * compression level, which must be between 0 and 9, or -1 for the default compression level * @param strategy * the compression strategy * @param noWrap * if true then use GZIP compatible compression * @return Size of the deflated stream * @throws IOException * @see #NO_COMPRESSION * @see #BEST_SPEED * @see #BEST_COMPRESSION */ public static int deflate(Stream in, Stream out, int compressionLevel, int strategy, boolean noWrap) throws IOException { if (in == null) throw new NullPointerException("Argument 'in' cannot have a null value"); if (out == null) throw new NullPointerException("Argument 'out' cannot have a null value"); if (compressionLevel < -1 || compressionLevel > 9) throw new IllegalArgumentException("Argument 'compressionLevel' must be between -1 and 9."); Deflater deflater = null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); deflater = new Deflater(compressionLevel, noWrap); deflater.setStrategy(strategy); DeflaterOutputStream dos = new DeflaterOutputStream(baos, deflater, 8192); byte[] bin = new byte[1024]; int r; while (true) { r = in.readBytes(bin, 0, bin.length); if (r > 0) dos.write(bin, 0, r); else break; } dos.close(); byte[] bout = baos.toByteArray(); out.writeBytes(bout, 0, bout.length); return bout.length; } catch (java.io.IOException e) { throw new IOException(e.getMessage()); } finally { /* * "Sun's Deflater class allocates some non-heap memory, which is only cleared with its end() call, or when its * finalizer is called. It's possible to fill up the non-heap memory when the deflater is used frequently * enough that finalizers don't get called in time." - Craig Fields */ if (deflater != null) //flsobral@tc115_76: Always finish the Deflater usage, instead of leaving this task for its finalizer. deflater.end(); } } /** * Attempts to fully read the given stream 'in', inflating and writing to the given stream 'out'. * * @param in * Deflated input stream * @param out * Inflated output stream * @return Size of the inflated stream * @throws IOException * @throws ZipException */ public static int inflate(Stream in, Stream out) throws IOException, ZipException { return inflate(in, out, -1, false); } /** * Attempts to read the number of bytes specified by 'sizeIn' from the the given stream 'in', inflating and writing * to the given stream 'out'. If 'sizeIn' is -1, it will attempt to fully read the stream. * * @param in * Deflated input stream * @param out * Inflated output stream * @param sizeIn * How many bytes to read, or -1 to read until <code>in</code>'s end * @return Size of the inflated stream * @throws ZipException * @throws IOException */ public static int inflate(Stream in, Stream out, int sizeIn) throws IOException, ZipException { return inflate(in, out, sizeIn, false); } /** * Attempts to read the number of bytes specified by 'sizeIn' from the the given stream 'in', inflating and writing * to the given stream 'out'. If 'sizeIn' is -1, it will attempt to fully read the stream. If the parameter 'noWrap' * is true then the ZLIB header and checksum fields will not be used. This provides compatibility with the * compression format used by both GZIP and PKZIP.<br> * Note: When using the 'noWrap' option it is also necessary to provide an extra "dummy" byte as input. This is * required by the ZLIB native library in order to support certain optimizations. * * @param in * Deflated input stream * @param out * Inflated output stream * @param sizeIn * How many bytes to read, or -1 to read until <code>in</code>'s end * @param noWrap * if true then support GZIP compatible compression * @return Size of the inflated stream * @throws ZipException * @throws IOException */ public static int inflate(Stream in, Stream out, int sizeIn, boolean noWrap) throws IOException, ZipException { if (in == null) throw new NullPointerException("Argument 'in' cannot have a null value"); if (out == null) throw new NullPointerException("Argument 'out' cannot have a null value"); if (sizeIn < -1) throw new IllegalArgumentException("Argument 'sizeIn' cannot have a value lower than -1."); if (sizeIn == 0) return 0; Inflater inf = new Inflater(noWrap); byte[] bin = new byte[Math.min(sizeIn <= 0 ? 1024 : sizeIn, 1024)]; byte[] bout = new byte[bin.length * 10]; int r = 0, w, rt = 0, wt = 0; int s = sizeIn; try { while (true) { int tor = sizeIn == -1 ? bin.length : Math.min(bin.length, s); if (tor > 0) r = in.readBytes(bin, 0, tor); // if tor == 0 and the stream does not quietly accept requests of size 0 (such as File stream) this call will throw exception if (r > 0) { inf.setInput(bin, 0, r); while (true) { w = inf.inflate(bout); if (w <= 0) break; out.writeBytes(bout, 0, w); wt += w; } rt += r; s -= r; r = 0; // reset r if (sizeIn > 0) sizeIn -= r; } else break; } } catch (totalcross.io.IOException e) { throw new IOException(e.getMessage()); } catch (java.util.zip.DataFormatException e) { throw new ZipException(e.getMessage()); } if (rt > 0 && (wt == 0 || (sizeIn > 0 && s > 0))) throw new ZipException("Inflate error: " + "Read " + rt + " but could not decompress it."); return wt; } private ZLib() { } // cannot instantiate native public static int deflate4D(Stream in, Stream out, int compressionLevel, int strategy, boolean noWrap) throws IOException; native public static int inflate4D(Stream in, Stream out, int sizeIn, boolean noWrap) throws IOException, ZipException;; }