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