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    *      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   * 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          @SuppressWarnings("resource") // caller closes
72          @Override
73          public ProxyOutputStream get() throws IOException {
74              return new ProxyOutputStream(getOutputStream());
75          }
76  
77      }
78  
79      /**
80       * Constructs a new ProxyOutputStream.
81       *
82       * @param delegate  the OutputStream to delegate to
83       */
84      public ProxyOutputStream(final OutputStream delegate) {
85          // the delegate is stored in a protected superclass variable named 'out'
86          super(delegate);
87      }
88  
89      /**
90       * Invoked by the write methods after the proxied call has returned
91       * successfully. The number of bytes written (1 for the
92       * {@link #write(int)} method, buffer length for {@link #write(byte[])},
93       * etc.) is given as an argument.
94       * <p>
95       * Subclasses can override this method to add common post-processing
96       * functionality without having to override all the write methods.
97       * The default implementation does nothing.
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
110      * of bytes to be written (1 for the {@link #write(int)} method, buffer
111      * length for {@link #write(byte[])}, etc.) is given as an argument.
112      * <p>
113      * Subclasses can override this method to add common pre-processing
114      * functionality without having to override all the write methods.
115      * The default implementation does nothing.
116      *
117      * @param n number of bytes to be written
118      * @throws IOException if the pre-processing fails
119      * @since 2.0
120      */
121     @SuppressWarnings("unused") // Possibly thrown from subclasses.
122     protected void beforeWrite(final int n) throws IOException {
123         // noop
124     }
125 
126     /**
127      * Invokes the delegate's {@code close()} method.
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      * @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
152      * handling. The default behavior is to re-throw the exception.
153      * @param e The IOException thrown
154      * @throws IOException if an I/O error occurs.
155      * @since 2.0
156      */
157     protected void handleIOException(final IOException e) throws IOException {
158         throw e;
159     }
160 
161     /**
162      * Sets the underlying output stream.
163      *
164      * @param out the underlying output stream.
165      * @return this instance.
166      * @since 2.19.0
167      */
168     public ProxyOutputStream setReference(final OutputStream out) {
169         this.out = out;
170         return this;
171     }
172 
173     /**
174      * Unwraps this instance by returning the underlying {@link OutputStream}.
175      * <p>
176      * Use with caution; useful to query the underlying {@link OutputStream}.
177      * </p>
178      *
179      * @return the underlying {@link OutputStream}.
180      */
181     OutputStream unwrap() {
182         return out;
183     }
184 
185     /**
186      * Invokes the delegate's {@code write(byte[])} method.
187      * @param bts the bytes to write
188      * @throws IOException if an I/O error occurs.
189      */
190     @Override
191     public void write(final byte[] bts) throws IOException {
192         try {
193             final int len = IOUtils.length(bts);
194             beforeWrite(len);
195             out.write(bts);
196             afterWrite(len);
197         } catch (final IOException e) {
198             handleIOException(e);
199         }
200     }
201 
202     /**
203      * Invokes the delegate's {@code write(byte[])} method.
204      * @param bts the bytes to write
205      * @param st The start offset
206      * @param end The number of bytes to write
207      * @throws IOException if an I/O error occurs.
208      */
209     @Override
210     public void write(final byte[] bts, final int st, final int end) throws IOException {
211         try {
212             beforeWrite(end);
213             out.write(bts, st, end);
214             afterWrite(end);
215         } catch (final IOException e) {
216             handleIOException(e);
217         }
218     }
219 
220     /**
221      * Invokes the delegate's {@code write(int)} method.
222      * @param idx the byte to write
223      * @throws IOException if an I/O error occurs.
224      */
225     @Override
226     public void write(final int idx) throws IOException {
227         try {
228             beforeWrite(1);
229             out.write(idx);
230             afterWrite(1);
231         } catch (final IOException e) {
232             handleIOException(e);
233         }
234     }
235 
236 }