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}