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