View Javadoc
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  package org.apache.commons.io.output;
18  
19  import java.io.FilterOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  
23  import org.apache.commons.io.IOUtils;
24  import org.apache.commons.io.build.AbstractStreamBuilder;
25  
26  /**
27   * OutputStream which breaks larger output blocks into chunks. Native code may need to copy the input array; if the write buffer is very large this can cause
28   * OOME.
29   * <p>
30   * To build an instance, see {@link Builder}
31   * </p>
32   *
33   * @see Builder
34   * @since 2.5
35   */
36  public class ChunkedOutputStream extends FilterOutputStream {
37  
38      // @formatter:off
39      /**
40       * Builds a new {@link UnsynchronizedByteArrayOutputStream}.
41       *
42       * <p>
43       * Using File IO:
44       * </p>
45       * <pre>{@code
46       * UnsynchronizedByteArrayOutputStream s = UnsynchronizedByteArrayOutputStream.builder()
47       *   .setBufferSize(8192)
48       *   .get();
49       * }
50       * </pre>
51       * <p>
52       * Using NIO Path:
53       * </p>
54       * <pre>{@code
55       * UnsynchronizedByteArrayOutputStream s = UnsynchronizedByteArrayOutputStream.builder()
56       *   .setBufferSize(8192)
57       *   .get();
58       * }
59       * </pre>
60       *
61       * @see #get()
62       * @since 2.13.0
63       */
64      // @formatter:on
65      public static class Builder extends AbstractStreamBuilder<ChunkedOutputStream, Builder> {
66  
67          /**
68           * Builds a new {@link UnsynchronizedByteArrayOutputStream}.
69           * <p>
70           * This builder use the following aspects:
71           * </p>
72           * <ul>
73           * <li>{@link #getInputStream()}</li>
74           * <li>{@link #getBufferSize()} (chunk size)</li>
75           * </ul>
76           *
77           * @return a new instance.
78           * @throws IllegalStateException         if the {@code origin} is {@code null}.
79           * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
80           * @throws IOException                   if an I/O error occurs.
81           * @see #getOutputStream()
82           * @see #getBufferSize()
83           */
84          @Override
85          public ChunkedOutputStream get() throws IOException {
86              return new ChunkedOutputStream(getOutputStream(), getBufferSize());
87          }
88  
89      }
90  
91      /**
92       * Constructs a new {@link Builder}.
93       *
94       * @return a new {@link Builder}.
95       * @since 2.13.0
96       */
97      public static Builder builder() {
98          return new Builder();
99      }
100 
101     /**
102      * The maximum chunk size to us when writing data arrays
103      */
104     private final int chunkSize;
105 
106     /**
107      * Constructs a new stream that uses a chunk size of {@link IOUtils#DEFAULT_BUFFER_SIZE}.
108      *
109      * @param stream the stream to wrap
110      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
111      */
112     @Deprecated
113     public ChunkedOutputStream(final OutputStream stream) {
114         this(stream, IOUtils.DEFAULT_BUFFER_SIZE);
115     }
116 
117     /**
118      * Constructs a new stream that uses the specified chunk size.
119      *
120      * @param stream    the stream to wrap
121      * @param chunkSize the chunk size to use; must be a positive number.
122      * @throws IllegalArgumentException if the chunk size is &lt;= 0
123      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
124      */
125     @Deprecated
126     public ChunkedOutputStream(final OutputStream stream, final int chunkSize) {
127         super(stream);
128         if (chunkSize <= 0) {
129             throw new IllegalArgumentException("chunkSize <= 0");
130         }
131         this.chunkSize = chunkSize;
132     }
133 
134     int getChunkSize() {
135         return chunkSize;
136     }
137 
138     /**
139      * Writes the data buffer in chunks to the underlying stream
140      *
141      * @param data      the data to write
142      * @param srcOffset the offset
143      * @param length    the length of data to write
144      *
145      * @throws IOException if an I/O error occurs.
146      */
147     @Override
148     public void write(final byte[] data, final int srcOffset, final int length) throws IOException {
149         int bytes = length;
150         int dstOffset = srcOffset;
151         while (bytes > 0) {
152             final int chunk = Math.min(bytes, chunkSize);
153             out.write(data, dstOffset, chunk);
154             bytes -= chunk;
155             dstOffset += chunk;
156         }
157     }
158 
159 }