4 * Copyright (C) 2016 Hiroshi Miura
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 import java.io.FilterOutputStream;
24 import java.io.IOException;
25 import java.util.zip.CRC32;
26 import java.util.zip.Deflater;
29 * Test of DictZipOutputStream.
30 * @author Hiroshi Miura
32 public class DictZipOutputStream extends FilterOutputStream {
35 * Compressor for this stream.
37 protected Deflater def;
40 * Output buffer for writing compressed data.
45 * CRC-32 of uncompressed data.
50 private boolean closed = false;
51 private long dataSize;
52 private DictZipHeader header;
53 private boolean usesDefaultDeflater = false;
54 private static final int bufLen = 58315;
58 * @param out output stream to filter.
59 * @param size total data of test file.
60 * @throws IOException if I/O error occored.
61 * @throws IllegalArgumentException if parameter is invalid.
63 public DictZipOutputStream(final RandomAccessOutputStream out, final long size)
64 throws IOException, IllegalArgumentException {
65 this(out, bufLen, size);
70 * @param out output stream to filter.
71 * @param buflen size of buffer to write.
72 * @param size total data of test file.
73 * @throws IOException if I/O error occored.
74 * @throws IllegalArgumentException if parameter is invalid.
76 public DictZipOutputStream(final RandomAccessOutputStream out, final int buflen,
77 final long size) throws IOException, IllegalArgumentException {
78 this(out, buflen, size, Deflater.DEFAULT_COMPRESSION);
83 * @param out output stream to filter.
84 * @param level level of compression, 9=best, 1=fast.
85 * @param buflen size of buffer to write.
86 * @param size total data of test file.
87 * @throws IOException if I/O error occored.
88 * @throws IllegalArgumentException if parameter is invalid.
90 public DictZipOutputStream(final RandomAccessOutputStream out, final int buflen,
91 final long size, final int level) throws IOException,
92 IllegalArgumentException {
93 this(out, new Deflater(level, true), buflen, size, level);
94 usesDefaultDeflater = true;
99 * @param out output stream to filter.
100 * @param defl custom deflater class, should be child of Deflater class.
101 * @param inBufferSize size of buffer to write.
102 * @param size total data of test file.
103 * @param level compression level.
104 * @throws IOException if I/O error occored.
105 * @throws IllegalArgumentException if parameter is invalid.
107 public DictZipOutputStream(final RandomAccessOutputStream out, final Deflater defl,
108 final int inBufferSize, final long size, final int level) throws IOException,
109 IllegalArgumentException {
111 if (out == null || defl == null) {
112 throw new NullPointerException();
114 if (inBufferSize <= 0) {
115 throw new IllegalArgumentException("buffer size <= 0");
118 throw new IllegalArgumentException("total data size <= 0");
122 int outBufferSize = (int) ((inBufferSize + 12) * 1.1);
123 buf = new byte[outBufferSize];
124 this.dataSize = size;
127 header = new DictZipHeader(dataSize, inBufferSize);
128 header.setMtime((long)System.currentTimeMillis()/1000);
130 case Deflater.DEFAULT_COMPRESSION:
131 header.setExtraFlag(DictZipHeader.CompressionLevel.DEFAULT_COMPRESSION);
132 defl.setLevel(level);
134 case Deflater.BEST_COMPRESSION:
135 header.setExtraFlag(DictZipHeader.CompressionLevel.BEST_COMPRESSION);
136 defl.setLevel(level);
138 case Deflater.BEST_SPEED:
139 header.setExtraFlag(DictZipHeader.CompressionLevel.BEST_SPEED);
140 defl.setLevel(level);
143 header.setExtraFlag(DictZipHeader.CompressionLevel.DEFAULT_COMPRESSION);
144 defl.setLevel(Deflater.DEFAULT_COMPRESSION);
146 header.setHeaderOS(DictZipHeader.OperatingSystem.UNIX);
153 * Closes the output stream.
155 * @exception IOException if an I/O error has occurred
158 public void close() throws IOException {
161 if (out instanceof RandomAccessOutputStream) {
162 RandomAccessOutputStream raout = (RandomAccessOutputStream) out;
166 if (usesDefaultDeflater) {
175 * Writes next block of compressed data to the output stream.
177 * @throws IOException if an I/O error has occurred
179 protected void deflate() throws IOException {
180 crc.update(buf, 0, buf.length);
181 int len = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH);
183 out.write(buf, 0, len);
184 header.chunks[cindex] = len;
190 * Writes an array of bytes to the compressed output stream.
192 * This method will block until all the bytes are written.
194 * @param b the data to be written
195 * @param off the start offset of the data
196 * @param len the length of the data
197 * @throws IOException if an I/O error has occurred
200 public synchronized void write(final byte[] b, final int off, final int len)
202 if (def.finished()) {
203 throw new IOException("write beyond end of stream");
205 if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
206 throw new IndexOutOfBoundsException();
207 } else if (def.getTotalIn() + len > dataSize) {
208 throw new IOException("write beyond decralated data size");
209 } else if (len == 0) {
212 if (!def.finished()) {
213 // Deflate no more than stride bytes at a time. This avoids
214 // excess copying in deflateBytes (see Deflater.c)
215 // as same as java.io.DeflaterOutputStream
216 int stride = buf.length;
217 for (int i = 0; i < len; i += stride) {
218 def.setInput(b, off + i, Math.min(stride, len - i));
219 while (!def.needsInput()) {
227 * Writes a byte to the compressed output stream.
229 * This method will block until the byte can be written.
231 * @param b the byte to be written
232 * @throws IOException if an I/O error has occurred
235 public synchronized void write(final int b) throws IOException {
236 // FIXME: implement for chunk handling
237 byte[] buf1 = new byte[1];
238 buf1[0] = (byte) (b & 0xff);
242 private static final int TRAILER_SIZE = 8;
245 * Finish compression, as same function as GZIPOutputStream.
246 * @throws IOException if I/O error occured.
248 public final void finish() throws IOException {
250 throw new IOException("Already closed!");
252 if (!def.finished()) {
254 while (!def.finished()) {
255 int len = def.deflate(buf, 0, buf.length);
256 if (def.finished() && len <= buf.length - TRAILER_SIZE) {
257 // last deflater buffer. Fit trailer at the end
258 writeTrailer(buf, len);
259 len = len + TRAILER_SIZE;
260 out.write(buf, 0, len);
264 out.write(buf, 0, len);
268 byte[] trailer = new byte[TRAILER_SIZE];
269 writeTrailer(trailer, 0);
273 private void writeHeader(RandomAccessOutputStream raout) throws IOException {
274 DictZipHeader.writeHeader(header, raout);
277 private void writeTrailer(final byte[] b, final int offset) throws IOException {
278 writeInt((int) crc.getValue(), b, offset); // CRC-32 of uncompr. data
279 writeInt(def.getTotalIn(), b, offset + 4); // Number of uncompr. bytes
283 * Writes integer in Intel byte order to a byte array, starting at a
286 private void writeInt(final int i, final byte[] b, final int offset) throws IOException {
287 writeShort(i & 0xffff, b, offset); // int low short val
288 writeShort((i >> 16) & 0xffff, b, offset + 2); // int high short val
292 * Writes short integer in Intel byte order to a byte array, starting
295 private void writeShort(final int s, final byte[] b, final int offset) throws IOException {
296 b[offset] = (byte) (s & 0xff); // low byte
297 b[offset + 1] = (byte) ((s >> 8) & 0xff); // high byte