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}