OSDN Git Service

Refactoring compression level and OS field
[dictzip-java/dictzip-java.git] / src / org / dict / zip / DictZipOutputStream.java
1 /*
2  * DictZip library.
3  *
4  * Copyright (C) 2016 Hiroshi Miura
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 package org.dict.zip;
22
23 import java.io.FilterOutputStream;
24 import java.io.IOException;
25 import java.util.zip.CRC32;
26 import java.util.zip.Deflater;
27
28 /**
29  * Test of DictZipOutputStream.
30  * @author Hiroshi Miura
31  */
32 public class DictZipOutputStream extends FilterOutputStream {
33
34     /**
35      * Compressor for this stream.
36      */
37     protected Deflater def;
38
39     /**
40      * Output buffer for writing compressed data.
41      */
42     protected byte[] buf;
43
44     /**
45      * CRC-32 of uncompressed data.
46      */
47     protected CRC32 crc;
48
49     private int cindex;
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;
55
56     /**
57      * Constructor.
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.
62      */
63     public DictZipOutputStream(final RandomAccessOutputStream out, final long size)
64             throws IOException, IllegalArgumentException {
65         this(out, bufLen, size);
66     }
67
68     /**
69      * Constructor.
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.
75      */
76     public DictZipOutputStream(final RandomAccessOutputStream out, final int buflen,
77             final long size) throws IOException, IllegalArgumentException {
78         this(out, buflen, size, Deflater.DEFAULT_COMPRESSION);
79     }
80
81     /**
82      * Constructor.
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.
89      */
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;
95     }
96
97    /**
98      * Constructor.
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.
106      */
107     public DictZipOutputStream(final RandomAccessOutputStream out, final Deflater defl,
108             final int inBufferSize, final long size, final int level) throws IOException,
109             IllegalArgumentException {
110         super(out);
111         if (out == null || defl == null) {
112             throw new NullPointerException();
113         }
114         if (inBufferSize <= 0) {
115             throw new IllegalArgumentException("buffer size <= 0");
116         }
117         if (size <= 0) {
118             throw new IllegalArgumentException("total data size <= 0");
119         }
120
121         this.def = defl;
122         int outBufferSize = (int) ((inBufferSize + 12) * 1.1); 
123         buf = new byte[outBufferSize];
124         this.dataSize = size;
125         crc = new CRC32();
126
127         header = new DictZipHeader(dataSize, inBufferSize);
128         header.setMtime((long)System.currentTimeMillis()/1000);
129         switch(level) {
130             case Deflater.DEFAULT_COMPRESSION:
131                 header.setExtraFlag(DictZipHeader.CompressionLevel.DEFAULT_COMPRESSION);
132                 defl.setLevel(level);
133                 break;
134             case Deflater.BEST_COMPRESSION:
135                 header.setExtraFlag(DictZipHeader.CompressionLevel.BEST_COMPRESSION);
136                 defl.setLevel(level);
137                 break;
138             case Deflater.BEST_SPEED:
139                 header.setExtraFlag(DictZipHeader.CompressionLevel.BEST_SPEED);
140                 defl.setLevel(level);
141                 break;
142             default:
143                 header.setExtraFlag(DictZipHeader.CompressionLevel.DEFAULT_COMPRESSION);
144                 defl.setLevel(Deflater.DEFAULT_COMPRESSION);
145         }
146         header.setHeaderOS(DictZipHeader.OperatingSystem.UNIX);
147         writeHeader(out);
148         crc.reset();
149         cindex = 0;
150     }
151
152     /**
153      * Closes the output stream.
154      *
155      * @exception IOException if an I/O error has occurred
156      */
157     @Override
158     public void close() throws IOException {
159         if (!closed) {
160             finish();
161             if (out instanceof RandomAccessOutputStream) {
162                 RandomAccessOutputStream raout = (RandomAccessOutputStream) out;
163                 raout.seek(0);
164                 writeHeader(raout);
165             }
166             if (usesDefaultDeflater) {
167                 def.end();
168             }
169             out.close();
170             closed = true;
171         }
172     }
173
174     /**
175      * Writes next block of compressed data to the output stream.
176      *
177      * @throws IOException if an I/O error has occurred
178      */
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);
182         if (len > 0) {
183             out.write(buf, 0, len);
184             header.chunks[cindex] = len;
185             cindex++;
186         }
187     }
188
189     /**
190      * Writes an array of bytes to the compressed output stream.
191      * <p>
192      * This method will block until all the bytes are written.
193      *
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
198      */
199     @Override
200     public synchronized void write(final byte[] b, final int off, final int len)
201             throws IOException {
202         if (def.finished()) {
203             throw new IOException("write beyond end of stream");
204         }
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) {
210             return;
211         }
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()) {
220                     deflate();
221                 }
222             }
223         }
224     }
225
226     /**
227      * Writes a byte to the compressed output stream.
228      * <p>
229      * This method will block until the byte can be written.
230      *
231      * @param b the byte to be written
232      * @throws IOException if an I/O error has occurred
233      */
234     @Override
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);
239         write(buf1, 0, 1);
240     }
241
242     private static final int TRAILER_SIZE = 8;
243
244     /**
245      * Finish compression, as same function as GZIPOutputStream.
246      * @throws IOException if I/O error occured.
247      */
248     public final void finish() throws IOException {
249         if (closed) {
250             throw new IOException("Already closed!");
251         }
252         if (!def.finished()) {
253             def.finish();
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);
261                     return;
262                 }
263                 if (len > 0) {
264                     out.write(buf, 0, len);
265                 }
266             }
267         }
268         byte[] trailer = new byte[TRAILER_SIZE];
269         writeTrailer(trailer, 0);
270         out.write(trailer);
271     }
272
273     private void writeHeader(RandomAccessOutputStream raout) throws IOException {
274         DictZipHeader.writeHeader(header, raout);
275     }
276
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
280     }
281
282     /*
283      * Writes integer in Intel byte order to a byte array, starting at a
284      * given offset.
285      */
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
289     }
290
291     /*
292      * Writes short integer in Intel byte order to a byte array, starting
293      * at a given offset
294      */
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
298     }
299 }