001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.io.output;
018
019import java.io.FilterOutputStream;
020import java.io.IOException;
021import java.io.OutputStream;
022
023import org.apache.commons.io.IOUtils;
024import org.apache.commons.io.build.AbstractStreamBuilder;
025
026/**
027 * A Proxy stream which acts as expected, that is it passes the method
028 * calls on to the proxied stream and doesn't change which methods are
029 * being called. It is an alternative base class to FilterOutputStream
030 * to increase reusability.
031 * <p>
032 * See the protected methods for ways in which a subclass can easily decorate
033 * a stream with custom pre-, post- or error processing functionality.
034 * </p>
035 */
036public class ProxyOutputStream extends FilterOutputStream {
037
038    /**
039     * Builds instances of {@link ProxyOutputStream}.
040     * <p>
041     * This class does not provide a convenience static {@code builder()} method so that subclasses can.
042     * </p>
043     *
044     * @since 2.19.0
045     */
046    public static class Builder extends AbstractStreamBuilder<ProxyOutputStream, Builder> {
047
048        /**
049         * Constructs a new builder of {@link ProxyOutputStream}.
050         */
051        public Builder() {
052            // empty
053        }
054
055        /**
056         * Builds a new {@link ProxyOutputStream}.
057         * <p>
058         * This builder uses the following aspects:
059         * </p>
060         * <ul>
061         * <li>{@link #getOutputStream()} is the target aspect.</li>
062         * </ul>
063         *
064         * @return a new instance.
065         * @throws IllegalStateException         if the {@code origin} is {@code null}.
066         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
067         * @throws IOException                   if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
068         * @see #getOutputStream()
069         * @see #getUnchecked()
070         */
071        @Override
072        public ProxyOutputStream get() throws IOException {
073            return new ProxyOutputStream(this);
074        }
075
076    }
077
078    @SuppressWarnings("resource") // caller closes
079    ProxyOutputStream(final Builder builder) throws IOException {
080        // the delegate is stored in a protected superclass variable named 'out'
081        super(builder.getOutputStream());
082    }
083
084    /**
085     * Constructs a new ProxyOutputStream.
086     *
087     * @param delegate  the OutputStream to delegate to
088     */
089    public ProxyOutputStream(final OutputStream delegate) {
090        // the delegate is stored in a protected superclass variable named 'out'
091        super(delegate);
092    }
093
094    /**
095     * Invoked by the write methods after the proxied call has returned
096     * successfully. The number of bytes written (1 for the
097     * {@link #write(int)} method, buffer length for {@link #write(byte[])},
098     * etc.) is given as an argument.
099     * <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}