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 <= 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 }