    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 */
    017
    018package org.apache.commons.io.output;
    019
    020import java.io.FilterOutputStream;
    021import java.io.IOException;
    022import java.io.OutputStream;
    023
    024import org.apache.commons.io.IOUtils;
    025import org.apache.commons.io.build.AbstractStreamBuilder;
    026
    027/**
    028 * An output stream proxy which delegates to the wrapped output stream.
    029 * <p>
    030 * See the protected methods for ways in which a subclass can easily decorate a stream with custom pre-, post- or error processing functionality.
    031 * </p>
    032 */
    033public class ProxyOutputStream extends FilterOutputStream {
    034
    035    /**
    036     * Builds instances of {@link ProxyOutputStream}.
    037     * <p>
    038     * This class does not provide a convenience static {@code builder()} method so that subclasses can.
    039     * </p>
    040     *
    041     * @since 2.19.0
    042     */
    043    public static class Builder extends AbstractStreamBuilder<ProxyOutputStream, Builder> {
    044
    045        /**
    046         * Constructs a new builder of {@link ProxyOutputStream}.
    047         */
    048        public Builder() {
    049            // empty
    050        }
    051
    052        /**
    053         * Builds a new {@link ProxyOutputStream}.
    054         * <p>
    055         * This builder uses the following aspects:
    056         * </p>
    057         * <ul>
    058         * <li>{@link #getOutputStream()} is the target aspect.</li>
    059         * </ul>
    060         *
    061         * @return a new instance.
    062         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    063         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
    064         * @throws IOException                   if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
    065         * @see #getOutputStream()
    066         * @see #getUnchecked()
    067         */
    068        @Override
    069        public ProxyOutputStream get() throws IOException {
    070            return new ProxyOutputStream(this);
    071        }
    072    }
    073
    074    @SuppressWarnings("resource") // caller closes
    075    ProxyOutputStream(final Builder builder) throws IOException {
    076        // the delegate is stored in a protected superclass variable named 'out'
    077        super(builder.getOutputStream());
    078    }
    079
    080    /**
    081     * Constructs a new ProxyOutputStream.
    082     *
    083     * @param delegate the OutputStream to delegate to.
    084     */
    085    public ProxyOutputStream(final OutputStream delegate) {
    086        // the delegate is stored in a protected superclass variable named 'out'
    087        super(delegate);
    088    }
    089
    090    /**
    091     * 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
    092     * length for {@link #write(byte[])}, etc.) is given as an argument.
    093     * <p>
    094     * Subclasses can override this method to add common post-processing functionality without having to override all the write methods. The default
    095     * implementation does nothing.
    096     * </p>
    097     *
    098     * @param n number of bytes written.
    099     * @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}