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