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;
024
025/**
026 * A Proxy stream which acts as expected, that is it passes the method
027 * calls on to the proxied stream and doesn't change which methods are
028 * being called. It is an alternative base class to FilterOutputStream
029 * to increase reusability.
030 * <p>
031 * See the protected methods for ways in which a subclass can easily decorate
032 * a stream with custom pre-, post- or error processing functionality.
033 * </p>
034 */
035public class ProxyOutputStream extends FilterOutputStream {
036
037    /**
038     * Constructs a new ProxyOutputStream.
039     *
040     * @param delegate  the OutputStream to delegate to
041     */
042    public ProxyOutputStream(final OutputStream delegate) {
043        // the delegate is stored in a protected superclass variable named 'out'
044        super(delegate);
045    }
046
047    /**
048     * Invoked by the write methods after the proxied call has returned
049     * successfully. The number of bytes written (1 for the
050     * {@link #write(int)} method, buffer length for {@link #write(byte[])},
051     * etc.) is given as an argument.
052     * <p>
053     * Subclasses can override this method to add common post-processing
054     * functionality without having to override all the write methods.
055     * The default implementation does nothing.
056     *
057     * @since 2.0
058     * @param n number of bytes written
059     * @throws IOException if the post-processing fails
060     */
061    @SuppressWarnings("unused") // Possibly thrown from subclasses.
062    protected void afterWrite(final int n) throws IOException {
063        // noop
064    }
065
066    /**
067     * Invoked by the write methods before the call is proxied. The number
068     * of bytes to be written (1 for the {@link #write(int)} method, buffer
069     * length for {@link #write(byte[])}, etc.) is given as an argument.
070     * <p>
071     * Subclasses can override this method to add common pre-processing
072     * functionality without having to override all the write methods.
073     * The default implementation does nothing.
074     *
075     * @since 2.0
076     * @param n number of bytes to be written
077     * @throws IOException if the pre-processing fails
078     */
079    @SuppressWarnings("unused") // Possibly thrown from subclasses.
080    protected void beforeWrite(final int n) throws IOException {
081        // noop
082    }
083
084    /**
085     * Invokes the delegate's {@code close()} method.
086     * @throws IOException if an I/O error occurs.
087     */
088    @Override
089    public void close() throws IOException {
090        IOUtils.close(out, this::handleIOException);
091    }
092
093    /**
094     * Invokes the delegate's {@code flush()} method.
095     * @throws IOException if an I/O error occurs.
096     */
097    @Override
098    public void flush() throws IOException {
099        try {
100            out.flush();
101        } catch (final IOException e) {
102            handleIOException(e);
103        }
104    }
105
106    /**
107     * Handle any IOExceptions thrown.
108     * <p>
109     * This method provides a point to implement custom exception
110     * handling. The default behavior is to re-throw the exception.
111     * @param e The IOException thrown
112     * @throws IOException if an I/O error occurs.
113     * @since 2.0
114     */
115    protected void handleIOException(final IOException e) throws IOException {
116        throw e;
117    }
118
119    /**
120     * Invokes the delegate's {@code write(byte[])} method.
121     * @param bts the bytes to write
122     * @throws IOException if an I/O error occurs.
123     */
124    @Override
125    public void write(final byte[] bts) throws IOException {
126        try {
127            final int len = IOUtils.length(bts);
128            beforeWrite(len);
129            out.write(bts);
130            afterWrite(len);
131        } catch (final IOException e) {
132            handleIOException(e);
133        }
134    }
135
136    /**
137     * Invokes the delegate's {@code write(byte[])} method.
138     * @param bts the bytes to write
139     * @param st The start offset
140     * @param end The number of bytes to write
141     * @throws IOException if an I/O error occurs.
142     */
143    @Override
144    public void write(final byte[] bts, final int st, final int end) throws IOException {
145        try {
146            beforeWrite(end);
147            out.write(bts, st, end);
148            afterWrite(end);
149        } catch (final IOException e) {
150            handleIOException(e);
151        }
152    }
153
154    /**
155     * Invokes the delegate's {@code write(int)} method.
156     * @param idx the byte to write
157     * @throws IOException if an I/O error occurs.
158     */
159    @Override
160    public void write(final int idx) throws IOException {
161        try {
162            beforeWrite(1);
163            out.write(idx);
164            afterWrite(1);
165        } catch (final IOException e) {
166            handleIOException(e);
167        }
168    }
169
170}