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