1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.zip;
19
20import java.io.FilterOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import java.util.Arrays;
24import libcore.io.Streams;
25
26/**
27 * This class provides an implementation of {@code FilterOutputStream} that
28 * compresses data using the <i>DEFLATE</i> algorithm. Basically it wraps the
29 * {@code Deflater} class and takes care of the buffering.
30 *
31 * @see Deflater
32 */
33public class DeflaterOutputStream extends FilterOutputStream {
34    static final int BUF_SIZE = 512;
35
36    /**
37     * The buffer for the data to be written to.
38     */
39    protected byte[] buf;
40
41    /**
42     * The deflater used.
43     */
44    protected Deflater def;
45
46    boolean done = false;
47
48    private final boolean syncFlush;
49
50    /**
51     * This constructor lets you pass the {@code Deflater} specifying the
52     * compression algorithm.
53     *
54     * @param os
55     *            is the {@code OutputStream} where to write the compressed data
56     *            to.
57     * @param def
58     *            is the specific {@code Deflater} that is used to compress
59     *            data.
60     */
61    public DeflaterOutputStream(OutputStream os, Deflater def) {
62        this(os, def, BUF_SIZE, false);
63    }
64
65    /**
66     * This is the most basic constructor. You only need to pass the {@code
67     * OutputStream} to which the compressed data shall be written to. The
68     * default settings for the {@code Deflater} and internal buffer are used.
69     * In particular the {@code Deflater} produces a ZLIB header in the output
70     * stream.
71     *
72     * @param os
73     *            is the OutputStream where to write the compressed data to.
74     */
75    public DeflaterOutputStream(OutputStream os) {
76        this(os, new Deflater(), BUF_SIZE, false);
77    }
78
79    /**
80     * This constructor lets you specify both the compression algorithm as well
81     * as the internal buffer size to be used.
82     *
83     * @param os
84     *            is the {@code OutputStream} where to write the compressed data
85     *            to.
86     * @param def
87     *            is the specific {@code Deflater} that will be used to compress
88     *            data.
89     * @param bsize
90     *            is the size to be used for the internal buffer.
91     */
92    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize) {
93        this(os, def, bsize, false);
94    }
95
96    /**
97     * @hide
98     * @since 1.7
99     */
100    public DeflaterOutputStream(OutputStream os, boolean syncFlush) {
101        this(os, new Deflater(), BUF_SIZE, syncFlush);
102    }
103
104    /**
105     * @hide
106     * @since 1.7
107     */
108    public DeflaterOutputStream(OutputStream os, Deflater def, boolean syncFlush) {
109        this(os, def, BUF_SIZE, syncFlush);
110    }
111
112    /**
113     * @hide
114     * @since 1.7
115     */
116    public DeflaterOutputStream(OutputStream os, Deflater def, int bsize, boolean syncFlush) {
117        super(os);
118        if (os == null) {
119            throw new NullPointerException("os == null");
120        } else if (def == null) {
121            throw new NullPointerException("def == null");
122        }
123        if (bsize <= 0) {
124            throw new IllegalArgumentException();
125        }
126        this.def = def;
127        this.syncFlush = syncFlush;
128        buf = new byte[bsize];
129    }
130
131    /**
132     * Compress the data in the input buffer and write it to the underlying
133     * stream.
134     *
135     * @throws IOException
136     *             If an error occurs during deflation.
137     */
138    protected void deflate() throws IOException {
139        int byteCount;
140        while ((byteCount = def.deflate(buf)) != 0) {
141            out.write(buf, 0, byteCount);
142        }
143    }
144
145    /**
146     * Writes any unwritten compressed data to the underlying stream, the closes
147     * all underlying streams. This stream can no longer be used after close()
148     * has been called.
149     *
150     * @throws IOException
151     *             If an error occurs while closing the data compression
152     *             process.
153     */
154    @Override
155    public void close() throws IOException {
156        // everything closed here should also be closed in ZipOutputStream.close()
157        if (!def.finished()) {
158            finish();
159        }
160        def.end();
161        out.close();
162    }
163
164    /**
165     * Writes any unwritten data to the underlying stream. Does not close the
166     * stream.
167     *
168     * @throws IOException
169     *             If an error occurs.
170     */
171    public void finish() throws IOException {
172        if (done) {
173            return;
174        }
175        def.finish();
176        while (!def.finished()) {
177            int byteCount = def.deflate(buf);
178            out.write(buf, 0, byteCount);
179        }
180        done = true;
181    }
182
183    @Override public void write(int i) throws IOException {
184        Streams.writeSingleByte(this, i);
185    }
186
187    /**
188     * Compresses {@code byteCount} bytes of data from {@code buf} starting at
189     * {@code offset} and writes it to the underlying stream.
190     * @throws IOException
191     *             If an error occurs during writing.
192     */
193    @Override public void write(byte[] buffer, int offset, int byteCount) throws IOException {
194        if (done) {
195            throw new IOException("attempt to write after finish");
196        }
197        Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);
198        if (!def.needsInput()) {
199            throw new IOException();
200        }
201        def.setInput(buffer, offset, byteCount);
202        deflate();
203    }
204
205    /**
206     * Flushes the underlying stream. This flushes only the bytes that can be
207     * compressed at the highest level.
208     *
209     * <p>For deflater output streams constructed with Java 7's
210     * {@code syncFlush} parameter set to true (not yet available on Android),
211     * this first flushes all outstanding data so that it may be immediately
212     * read by its recipient. Doing so may degrade compression.
213     */
214    @Override public void flush() throws IOException {
215        if (syncFlush) {
216            int byteCount;
217            while ((byteCount = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH)) != 0) {
218                out.write(buf, 0, byteCount);
219            }
220        }
221        out.flush();
222    }
223}
224