1d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// Copyright 2016 Google Inc. All rights reserved.
2d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden//
3d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// Licensed under the Apache License, Version 2.0 (the "License");
4d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// you may not use this file except in compliance with the License.
5d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// You may obtain a copy of the License at
6d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden//
7d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden//     http://www.apache.org/licenses/LICENSE-2.0
8d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden//
9d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// Unless required by applicable law or agreed to in writing, software
10d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// distributed under the License is distributed on an "AS IS" BASIS,
11d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// See the License for the specific language governing permissions and
13d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden// limitations under the License.
14d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
15d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenpackage com.google.archivepatcher.shared;
16d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
17d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenimport java.io.Closeable;
18d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenimport java.io.IOException;
19d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenimport java.io.InputStream;
20d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenimport java.io.OutputStream;
21d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
22d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden/**
23d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden * A pipe that moves data from an {@link InputStream} to an {@link OutputStream}, optionally
24d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden * uncompressing the input data on-the-fly.
25d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden */
26d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Haydenpublic class PartiallyUncompressingPipe implements Closeable {
27d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
28d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * The uncompressor used to uncompress compressed input streams.
29d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
30d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  private final DeflateUncompressor uncompressor;
31d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
32d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
33d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * The output stream to write to.
34d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
35d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  private final CountingOutputStream out;
36d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
37d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
38d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * A buffer used when copying bytes.
39d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
40d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  private final byte[] copyBuffer;
41d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
42d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
43d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * Modes available for {@link PartiallyUncompressingPipe#pipe(InputStream, Mode)}.
44d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
45d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  public static enum Mode {
46d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    /**
47d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     * Copy bytes form the {@link InputStream} to the {@link OutputStream} without modification.
48d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     */
49d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    COPY,
50d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
51d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    /**
52d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     * Treat the {@link InputStream} as a deflate stream with nowrap=false, uncompress the bytes
53d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     * on-the-fly and write the uncompressed data to the {@link OutputStream}.
54d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     */
55d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    UNCOMPRESS_WRAPPED,
56d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
57d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    /**
58d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     * Treat the {@link InputStream} as a deflate stream with nowrap=true, uncompress the bytes
59d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     * on-the-fly and write the uncompressed data to the {@link OutputStream}.
60d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden     */
61d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    UNCOMPRESS_NOWRAP,
62d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  }
63d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
64d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
65d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * Constructs a new stream.
66d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @param out the stream, to write to
67d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @param copyBufferSize the size of the buffer to use when copying instead of uncompressing
68d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
69d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  public PartiallyUncompressingPipe(OutputStream out, int copyBufferSize) {
70d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    this.out = new CountingOutputStream(out);
71d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    uncompressor = new DeflateUncompressor();
72d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    uncompressor.setCaching(true);
73d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    copyBuffer = new byte[copyBufferSize];
74d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  }
75d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
76d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
77d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * Pipes the entire contents of the specified {@link InputStream} to the configured
78d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * {@link OutputStream}, optionally uncompressing on-the-fly.
79d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @param in the stream to read from
80d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @param mode the mode to use for reading and writing
81d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @return the number of bytes written to the output stream
82d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @throws IOException if anything goes wrong
83d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
84d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  public long pipe(InputStream in, Mode mode) throws IOException {
85d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    long bytesWrittenBefore = out.getNumBytesWritten();
86d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    if (mode == Mode.COPY) {
87d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden      int numRead = 0;
88d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden      while ((numRead = in.read(copyBuffer)) >= 0) {
89d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden        out.write(copyBuffer, 0, numRead);
90d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden      }
91d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    } else {
92d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden      uncompressor.setNowrap(mode == Mode.UNCOMPRESS_NOWRAP);
93d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden      uncompressor.uncompress(in, out);
94d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    }
95d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    out.flush();
96d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    return out.getNumBytesWritten() - bytesWrittenBefore;
97d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  }
98d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
99d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  /**
100d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * Returns the number of bytes written to the stream so far.
101d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   * @return as described
102d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden   */
103d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  public long getNumBytesWritten() {
104d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    return out.getNumBytesWritten();
105d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  }
106d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden
107d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  @Override
108d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  public void close() throws IOException {
109d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    uncompressor.release();
110d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden    out.close();
111d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden  }
112d84e46c30d0ea027d047ee4c75c040a173ec9ffcAndrew Hayden}
113