    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.FilterWriter;
    020import java.io.IOException;
    021import java.io.Writer;
    022
    023import org.apache.commons.io.IOUtils;
    024
    025/**
    026 * A writer proxy which delegates to the wrapped writer.
    027 * <p>
    028 * It is an alternative base class to FilterWriter to increase reusability, because FilterWriter changes the methods being called, such as
    029 * {@code write(char[]) to write(char[], int, int)} and {@code write(String) to write(String, int, int)}.
    030 * </p>
    031 */
    032public class ProxyWriter extends FilterWriter {
    033
    034    /**
    035     * Constructs a new ProxyWriter.
    036     *
    037     * @param delegate  the Writer to delegate to.
    038     */
    039    public ProxyWriter(final Writer delegate) {
    040        // the delegate is stored in a protected superclass variable named 'out'
    041        super(delegate);
    042    }
    043
    044    /**
    045     * Invoked by the write methods after the proxied call has returned
    046     * successfully. The number of chars written (1 for the
    047     * {@link #write(int)} method, buffer length for {@link #write(char[])},
    048     * etc.) is given as an argument.
    049     * <p>
    050     * Subclasses can override this method to add common post-processing
    051     * functionality without having to override all the write methods.
    052     * The default implementation does nothing.
    053     * </p>
    054     *
    055     * @param n number of chars written.
    056     * @throws IOException if the post-processing fails.
    057     * @since 2.0
    058     */
    059    @SuppressWarnings("unused") // Possibly thrown from subclasses.
    060    protected void afterWrite(final int n) throws IOException {
    061        // noop
    062    }
    063
    064    /**
    065     * Invokes the delegate's {@code append(char)} method.
    066     *
    067     * @param c The character to write.
    068     * @return this writer.
    069     * @throws IOException if an I/O error occurs.
    070     * @since 2.0
    071     */
    072    @Override
    073    public Writer append(final char c) throws IOException {
    074        try {
    075            beforeWrite(1);
    076            out.append(c);
    077            afterWrite(1);
    078        } catch (final IOException e) {
    079            handleIOException(e);
    080        }
    081        return this;
    082    }
    083
    084    /**
    085     * Invokes the delegate's {@code append(CharSequence)} method.
    086     *
    087     * @param csq The character sequence to write.
    088     * @return this writer.
    089     * @throws IOException if an I/O error occurs.
    090     * @since 2.0
    091     */
    092    @Override
    093    public Writer append(final CharSequence csq) throws IOException {
    094        try {
    095            final int len = IOUtils.length(csq);
    096            beforeWrite(len);
    097            out.append(csq);
    098            afterWrite(len);
    099        } catch (final IOException e) {
    100            handleIOException(e);
    101        }
    102        return this;
    103    }
    104
    105    /**
    106     * Invokes the delegate's {@code append(CharSequence, int, int)} method.
    107     *
    108     * @param csq The character sequence to write.
    109     * @param start The index of the first character to write.
    110     * @param end  The index of the first character to write (exclusive).
    111     * @return this writer.
    112     * @throws IOException if an I/O error occurs.
    113     * @since 2.0
    114     */
    115    @Override
    116    public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
    117        try {
    118            beforeWrite(end - start);
    119            out.append(csq, start, end);
    120            afterWrite(end - start);
    121        } catch (final IOException e) {
    122            handleIOException(e);
    123        }
    124        return this;
    125    }
    126
    127    /**
    128     * Invoked by the write methods before the call is proxied. The number
    129     * of chars to be written (1 for the {@link #write(int)} method, buffer
    130     * length for {@link #write(char[])}, etc.) is given as an argument.
    131     * <p>
    132     * Subclasses can override this method to add common pre-processing
    133     * functionality without having to override all the write methods.
    134     * The default implementation does nothing.
    135     * </p>
    136     *
    137     * @param n number of chars to be written.
    138     * @throws IOException if the pre-processing fails.
    139     * @since 2.0
    140     */
    141    @SuppressWarnings("unused") // Possibly thrown from subclasses.
    142    protected void beforeWrite(final int n) throws IOException {
    143        // noop
    144    }
    145
    146    /**
    147     * Invokes the delegate's {@code close()} method.
    148     *
    149     * @throws IOException if an I/O error occurs.
    150     */
    151    @Override
    152    public void close() throws IOException {
    153        IOUtils.close(out, this::handleIOException);
    154    }
    155
    156    /**
    157     * Invokes the delegate's {@code flush()} method.
    158     *
    159     * @throws IOException if an I/O error occurs.
    160     */
    161    @Override
    162    public void flush() throws IOException {
    163        try {
    164            out.flush();
    165        } catch (final IOException e) {
    166            handleIOException(e);
    167        }
    168    }
    169
    170    /**
    171     * Handles any IOExceptions thrown.
    172     * <p>
    173     * This method provides a point to implement custom exception
    174     * handling. The default behavior is to re-throw the exception.
    175     * </p>
    176     *
    177     * @param e The IOException thrown.
    178     * @throws IOException if an I/O error occurs.
    179     * @since 2.0
    180     */
    181    protected void handleIOException(final IOException e) throws IOException {
    182        throw e;
    183    }
    184
    185    /**
    186     * Sets the underlying writer.
    187     * <p>
    188     * Use with caution.
    189     * </p>
    190     *
    191     * @param out the underlying output writer.
    192     * @return {@code this} instance.
    193     * @since 2.22.0
    194     */
    195    public ProxyWriter setReference(final Writer out) {
    196        this.out = out;
    197        return this;
    198    }
    199
    200    /**
    201     * Unwraps this instance by returning the underlying {@link Writer}.
    202     * <p>
    203     * Use with caution.
    204     * </p>
    205     *
    206     * @return the underlying {@link Writer}.
    207     * @since 2.22.0
    208     */
    209    public Writer unwrap() {
    210        return out;
    211    }
    212
    213    /**
    214     * Invokes the delegate's {@code write(char[])} method.
    215     *
    216     * @param cbuf the characters to write.
    217     * @throws IOException if an I/O error occurs.
    218     */
    219    @Override
    220    public void write(final char[] cbuf) throws IOException {
    221        try {
    222            final int len = IOUtils.length(cbuf);
    223            beforeWrite(len);
    224            out.write(cbuf);
    225            afterWrite(len);
    226        } catch (final IOException e) {
    227            handleIOException(e);
    228        }
    229    }
    230
    231    /**
    232     * Invokes the delegate's {@code write(char[], int, int)} method.
    233     *
    234     * @param cbuf the characters to write.
    235     * @param off The start offset.
    236     * @param len The number of characters to write.
    237     * @throws IOException if an I/O error occurs.
    238     */
    239    @Override
    240    public void write(final char[] cbuf, final int off, final int len) throws IOException {
    241        try {
    242            beforeWrite(len);
    243            out.write(cbuf, off, len);
    244            afterWrite(len);
    245        } catch (final IOException e) {
    246            handleIOException(e);
    247        }
    248    }
    249
    250    /**
    251     * Invokes the delegate's {@code write(int)} method.
    252     *
    253     * @param c the character to write.
    254     * @throws IOException if an I/O error occurs.
    255     */
    256    @Override
    257    public void write(final int c) throws IOException {
    258        try {
    259            beforeWrite(1);
    260            out.write(c);
    261            afterWrite(1);
    262        } catch (final IOException e) {
    263            handleIOException(e);
    264        }
    265    }
    266
    267    /**
    268     * Invokes the delegate's {@code write(String)} method.
    269     *
    270     * @param str the string to write.
    271     * @throws IOException if an I/O error occurs.
    272     */
    273    @Override
    274    public void write(final String str) throws IOException {
    275        try {
    276            final int len = IOUtils.length(str);
    277            beforeWrite(len);
    278            out.write(str);
    279            afterWrite(len);
    280        } catch (final IOException e) {
    281            handleIOException(e);
    282        }
    283    }
    284
    285    /**
    286     * Invokes the delegate's {@code write(String)} method.
    287     *
    288     * @param str the string to write.
    289     * @param off The start offset.
    290     * @param len The number of characters to write.
    291     * @throws IOException if an I/O error occurs.
    292     */
    293    @Override
    294    public void write(final String str, final int off, final int len) throws IOException {
    295        try {
    296            beforeWrite(len);
    297            out.write(str, off, len);
    298            afterWrite(len);
    299        } catch (final IOException e) {
    300            handleIOException(e);
    301        }
    302    }
    303
    304}