    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}