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
18 package org.apache.commons.io.output;
19
20 import java.io.FilterOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23
24 import org.apache.commons.io.IOUtils;
25 import org.apache.commons.io.build.AbstractStreamBuilder;
26
27 /**
28 * An output stream proxy which delegates to the wrapped output stream.
29 * <p>
30 * See the protected methods for ways in which a subclass can easily decorate a stream with custom pre-, post- or error processing functionality.
31 * </p>
32 */
33 public class ProxyOutputStream extends FilterOutputStream {
34
35 /**
36 * Builds instances of {@link ProxyOutputStream}.
37 * <p>
38 * This class does not provide a convenience static {@code builder()} method so that subclasses can.
39 * </p>
40 *
41 * @since 2.19.0
42 */
43 public static class Builder extends AbstractStreamBuilder<ProxyOutputStream, Builder> {
44
45 /**
46 * Constructs a new builder of {@link ProxyOutputStream}.
47 */
48 public Builder() {
49 // empty
50 }
51
52 /**
53 * Builds a new {@link ProxyOutputStream}.
54 * <p>
55 * This builder uses the following aspects:
56 * </p>
57 * <ul>
58 * <li>{@link #getOutputStream()} is the target aspect.</li>
59 * </ul>
60 *
61 * @return a new instance.
62 * @throws IllegalStateException if the {@code origin} is {@code null}.
63 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
64 * @throws IOException if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
65 * @see #getOutputStream()
66 * @see #getUnchecked()
67 */
68 @Override
69 public ProxyOutputStream get() throws IOException {
70 return new ProxyOutputStream(this);
71 }
72 }
73
74 @SuppressWarnings("resource") // caller closes
75 ProxyOutputStream(final Builder builder) throws IOException {
76 // the delegate is stored in a protected superclass variable named 'out'
77 super(builder.getOutputStream());
78 }
79
80 /**
81 * Constructs a new ProxyOutputStream.
82 *
83 * @param delegate the OutputStream to delegate to.
84 */
85 public ProxyOutputStream(final OutputStream delegate) {
86 // the delegate is stored in a protected superclass variable named 'out'
87 super(delegate);
88 }
89
90 /**
91 * Invoked by the write methods after the proxied call has returned successfully. The number of bytes written (1 for the {@link #write(int)} method, buffer
92 * length for {@link #write(byte[])}, etc.) is given as an argument.
93 * <p>
94 * Subclasses can override this method to add common post-processing functionality without having to override all the write methods. The default
95 * implementation does nothing.
96 * </p>
97 *
98 * @param n number of bytes written.
99 * @throws IOException if the post-processing fails.
100 * @since 2.0
101 */
102 @SuppressWarnings("unused") // Possibly thrown from subclasses.
103 protected void afterWrite(final int n) throws IOException {
104 // noop
105 }
106
107 /**
108 * Invoked by the write methods before the call is proxied. The number of bytes to be written (1 for the {@link #write(int)} method, buffer length for
109 * {@link #write(byte[])}, etc.) is given as an argument.
110 * <p>
111 * Subclasses can override this method to add common pre-processing functionality without having to override all the write methods. The default
112 * implementation does nothing.
113 * </p>
114 *
115 * @param n number of bytes to be written.
116 * @throws IOException if the pre-processing fails.
117 * @since 2.0
118 */
119 @SuppressWarnings("unused") // Possibly thrown from subclasses.
120 protected void beforeWrite(final int n) throws IOException {
121 // noop
122 }
123
124 /**
125 * Invokes the delegate's {@code close()} method.
126 *
127 * @throws IOException if an I/O error occurs.
128 */
129 @Override
130 public void close() throws IOException {
131 IOUtils.close(out, this::handleIOException);
132 }
133
134 /**
135 * Invokes the delegate's {@code flush()} method.
136 *
137 * @throws IOException if an I/O error occurs.
138 */
139 @Override
140 public void flush() throws IOException {
141 try {
142 out.flush();
143 } catch (final IOException e) {
144 handleIOException(e);
145 }
146 }
147
148 /**
149 * Handle any IOExceptions thrown.
150 * <p>
151 * This method provides a point to implement custom exception. handling. The default behavior is to re-throw the exception.
152 * </p>
153 *
154 * @param e The IOException thrown.
155 * @throws IOException if an I/O error occurs.
156 * @since 2.0
157 */
158 protected void handleIOException(final IOException e) throws IOException {
159 throw e;
160 }
161
162 /**
163 * Sets the underlying output stream.
164 * <p>
165 * Use with caution.
166 * </p>
167 *
168 * @param out the underlying output stream.
169 * @return {@code this} instance.
170 * @since 2.19.0
171 */
172 public ProxyOutputStream setReference(final OutputStream out) {
173 this.out = out;
174 return this;
175 }
176
177 /**
178 * Unwraps this instance by returning the underlying {@link OutputStream}.
179 * <p>
180 * Use with caution.
181 * </p>
182 *
183 * @return the underlying {@link OutputStream}.
184 * @since 2.22.0
185 */
186 public OutputStream unwrap() {
187 return out;
188 }
189
190 /**
191 * Invokes the delegate's {@code write(byte[])} method.
192 *
193 * @param b the bytes to write.
194 * @throws IOException if an I/O error occurs.
195 */
196 @Override
197 public void write(final byte[] b) throws IOException {
198 try {
199 final int len = IOUtils.length(b);
200 beforeWrite(len);
201 out.write(b);
202 afterWrite(len);
203 } catch (final IOException e) {
204 handleIOException(e);
205 }
206 }
207
208 /**
209 * Invokes the delegate's {@code write(byte[])} method.
210 *
211 * @param b the bytes to write.
212 * @param off The start offset.
213 * @param len The number of bytes to write.
214 * @throws IOException if an I/O error occurs.
215 */
216 @Override
217 public void write(final byte[] b, final int off, final int len) throws IOException {
218 try {
219 beforeWrite(len);
220 out.write(b, off, len);
221 afterWrite(len);
222 } catch (final IOException e) {
223 handleIOException(e);
224 }
225 }
226
227 /**
228 * Invokes the delegate's {@code write(int)} method.
229 *
230 * @param b the byte to write.
231 * @throws IOException if an I/O error occurs.
232 */
233 @Override
234 public void write(final int b) throws IOException {
235 try {
236 beforeWrite(1);
237 out.write(b);
238 afterWrite(1);
239 } catch (final IOException e) {
240 handleIOException(e);
241 }
242 }
243
244 /**
245 * Invokes the delegate's {@code write(byte[])} method for the {@code repeat} count.
246 *
247 * @param b the bytes to write.
248 * @param off The start offset.
249 * @param len The number of bytes to write.
250 * @param repeat How many times to write the bytes in {@code b}.
251 * @throws IOException if an I/O error occurs.
252 * @since 2.21.0
253 */
254 public void writeRepeat(final byte[] b, final int off, final int len, final long repeat) throws IOException {
255 long remains = repeat;
256 while (remains-- > 0) {
257 write(b, off, len);
258 }
259 }
260
261 /**
262 * Invokes the delegate's {@code write(byte[])} method for the {@code repeat} count.
263 *
264 * @param b the bytes to write.
265 * @param repeat How many times to write the bytes in {@code b}.
266 * @throws IOException if an I/O error occurs.
267 * @since 2.21.0
268 */
269 public void writeRepeat(final byte[] b, final long repeat) throws IOException {
270 long remains = repeat;
271 while (remains-- > 0) {
272 write(b);
273 }
274 }
275
276 /**
277 * Invokes the delegate's {@code write(int)} method.
278 *
279 * @param b the byte to write.
280 * @param repeat How many times to write the byte in {@code b}.
281 * @throws IOException if an I/O error occurs.
282 * @since 2.21.0
283 */
284 public void writeRepeat(final int b, final long repeat) throws IOException {
285 long remains = repeat;
286 while (remains-- > 0) {
287 write(b);
288 }
289 }
290 }