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       * 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(getOutputStream(), getBufferSize());
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 a chunk size of {@link IOUtils#DEFAULT_BUFFER_SIZE}.
118      *
119      * @param stream the stream to wrap
120      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
121      */
122     @Deprecated
123     public ChunkedOutputStream(final OutputStream stream) {
124         this(stream, IOUtils.DEFAULT_BUFFER_SIZE);
125     }
126 
127     /**
128      * Constructs a new stream that uses the specified chunk size.
129      *
130      * @param stream    the stream to wrap
131      * @param chunkSize the chunk size to use; must be a positive number.
132      * @throws IllegalArgumentException if the chunk size is &lt;= 0
133      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
134      */
135     @Deprecated
136     public ChunkedOutputStream(final OutputStream stream, final int chunkSize) {
137         super(stream);
138         if (chunkSize <= 0) {
139             throw new IllegalArgumentException("chunkSize <= 0");
140         }
141         this.chunkSize = chunkSize;
142     }
143 
144     int getChunkSize() {
145         return chunkSize;
146     }
147 
148     /**
149      * Writes the data buffer in chunks to the underlying stream
150      *
151      * @param data      the data to write
152      * @param srcOffset the offset
153      * @param length    the length of data to write
154      * @throws IOException if an I/O error occurs.
155      */
156     @Override
157     public void write(final byte[] data, final int srcOffset, final int length) throws IOException {
158         int bytes = length;
159         int dstOffset = srcOffset;
160         while (bytes > 0) {
161             final int chunk = Math.min(bytes, chunkSize);
162             out.write(data, dstOffset, chunk);
163             bytes -= chunk;
164             dstOffset += chunk;
165         }
166     }
167 
168 }