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