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