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    *      https://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       * ChunkedOutputStream s = ChunkedOutputStream.builder()
47       *   .setPath("over/there.out")
48       *   .setBufferSize(8192)
49       *   .get();
50       * }
51       * </pre>
52       * <p>
53       * Using NIO Path:
54       * </p>
55       * <pre>{@code
56       * ChunkedOutputStream s = ChunkedOutputStream.builder()
57       *   .setPath("over/there.out")
58       *   .setBufferSize(8192)
59       *   .get();
60       * }
61       * </pre>
62       *
63       * @see #get()
64       * @since 2.13.0
65       */
66      // @formatter:on
67      public static class Builder extends AbstractStreamBuilder<ChunkedOutputStream, Builder> {
68  
69          /**
70           * Constructs a new builder of {@link ChunkedOutputStream}.
71           */
72          public Builder() {
73              // empty
74          }
75  
76          /**
77           * Builds a new {@link ChunkedOutputStream}.
78           * <p>
79           * This builder uses the following aspects:
80           * </p>
81           * <ul>
82           * <li>{@link #getOutputStream()} is the target aspect.</li>
83           * <li>{@link #getBufferSize()} is used for the chunk size.</li>
84           * </ul>
85           *
86           * @return a new instance.
87           * @throws IllegalStateException         if the {@code origin} is {@code null}.
88           * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
89           * @throws IOException                   if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
90           * @see #getOutputStream()
91           * @see #getBufferSize()
92           * @see #getUnchecked()
93           */
94          @Override
95          public ChunkedOutputStream get() throws IOException {
96              return new ChunkedOutputStream(this);
97          }
98  
99      }
100 
101     /**
102      * Constructs a new {@link Builder}.
103      *
104      * @return a new {@link Builder}.
105      * @since 2.13.0
106      */
107     public static Builder builder() {
108         return new Builder();
109     }
110 
111     /**
112      * The maximum chunk size to us when writing data arrays
113      */
114     private final int chunkSize;
115 
116     /**
117      * Constructs a new stream that uses the specified chunk size.
118      *
119      * @param builder holds contruction data.
120      * @throws IOException if an I/O error occurs.
121      */
122     @SuppressWarnings("resource") // caller closes.
123     private ChunkedOutputStream(final Builder builder) throws IOException {
124         super(builder.getOutputStream());
125         final int bufferSize = builder.getBufferSize();
126         if (bufferSize <= 0) {
127             throw new IllegalArgumentException("chunkSize <= 0");
128         }
129         this.chunkSize = bufferSize;
130     }
131 
132     /**
133      * Constructs a new stream that uses a chunk size of {@link IOUtils#DEFAULT_BUFFER_SIZE}.
134      *
135      * @param stream the stream to wrap
136      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
137      */
138     @Deprecated
139     public ChunkedOutputStream(final OutputStream stream) {
140         this(stream, IOUtils.DEFAULT_BUFFER_SIZE);
141     }
142 
143     /**
144      * Constructs a new stream that uses the specified chunk size.
145      *
146      * @param stream    the stream to wrap
147      * @param chunkSize the chunk size to use; must be a positive number.
148      * @throws IllegalArgumentException if the chunk size is &lt;= 0
149      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
150      */
151     @Deprecated
152     public ChunkedOutputStream(final OutputStream stream, final int chunkSize) {
153         super(stream);
154         if (chunkSize <= 0) {
155             throw new IllegalArgumentException("chunkSize <= 0");
156         }
157         this.chunkSize = chunkSize;
158     }
159 
160     /* Package-private for testing. */
161     int getChunkSize() {
162         return chunkSize;
163     }
164 
165     /**
166      * Writes the data buffer in chunks to the underlying stream
167      *
168      * @param data      the data to write
169      * @param srcOffset the offset
170      * @param length    the length of data to write
171      * @throws IOException if an I/O error occurs.
172      */
173     @Override
174     public void write(final byte[] data, final int srcOffset, final int length) throws IOException {
175         int bytes = length;
176         int dstOffset = srcOffset;
177         while (bytes > 0) {
178             final int chunk = Math.min(bytes, chunkSize);
179             out.write(data, dstOffset, chunk);
180             bytes -= chunk;
181             dstOffset += chunk;
182         }
183     }
184 
185 }