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