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 *      http://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        @SuppressWarnings("resource") // caller closes
072        @Override
073        public ProxyOutputStream get() throws IOException {
074            return new ProxyOutputStream(getOutputStream());
075        }
076
077    }
078
079    /**
080     * Constructs a new ProxyOutputStream.
081     *
082     * @param delegate  the OutputStream to delegate to
083     */
084    public ProxyOutputStream(final OutputStream delegate) {
085        // the delegate is stored in a protected superclass variable named 'out'
086        super(delegate);
087    }
088
089    /**
090     * Invoked by the write methods after the proxied call has returned
091     * successfully. The number of bytes written (1 for the
092     * {@link #write(int)} method, buffer length for {@link #write(byte[])},
093     * etc.) is given as an argument.
094     * <p>
095     * Subclasses can override this method to add common post-processing
096     * functionality without having to override all the write methods.
097     * The default implementation does nothing.
098     *
099     * @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}