    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.input;
    019
    020import static org.apache.commons.io.IOUtils.EOF;
    021
    022import java.io.IOException;
    023import java.io.Reader;
    024
    025import org.apache.commons.io.IOUtils;
    026
    027/**
    028 * A {@link Reader} without any of the superclass' synchronization.
    029 *
    030 * @since 2.17.0
    031 */
    032public abstract class UnsynchronizedReader extends Reader {
    033
    034    /**
    035     * The maximum skip-buffer size.
    036     */
    037    private static final int MAX_SKIP_BUFFER_SIZE = IOUtils.DEFAULT_BUFFER_SIZE;
    038
    039    /**
    040     * Whether {@link #close()} completed successfully.
    041     */
    042    private boolean closed;
    043
    044    /**
    045     * The skip buffer, defaults to null until allocated in {@link UnsynchronizedReader#skip(long)}.
    046     */
    047    private char skipBuffer[];
    048
    049    /**
    050     * Constructs a new instance.
    051     */
    052    public UnsynchronizedReader() {
    053        // empty
    054    }
    055
    056    /**
    057     * Checks if this instance is closed and throws an IOException if so.
    058     *
    059     * @throws IOException if this instance is closed.
    060     */
    061    void checkOpen() throws IOException {
    062        Input.checkOpen(!isClosed());
    063    }
    064
    065    @Override
    066    public void close() throws IOException {
    067        closed = true;
    068    }
    069
    070    /**
    071     * Tests whether this instance is closed; if {@link #close()} completed successfully.
    072     *
    073     * @return whether this instance is closed.
    074     */
    075    public boolean isClosed() {
    076        return closed;
    077    }
    078
    079    /**
    080     * Sets whether this instance is closed.
    081     *
    082     * @param closed whether this instance is closed.
    083     */
    084    public void setClosed(final boolean closed) {
    085        this.closed = closed;
    086    }
    087
    088    /**
    089     * Skips characters by reading from this instance.
    090     *
    091     * This method will <em>block</em> until:
    092     * <ul>
    093     * <li>some characters are available,</li>
    094     * <li>an I/O error occurs, or</li>
    095     * <li>the end of the stream is reached.</li>
    096     * </ul>
    097     *
    098     * @param n The number of characters to skip.
    099     * @return The number of characters actually skipped.
    100     * @throws IllegalArgumentException If {@code n} is negative.
    101     * @throws IOException              If an I/O error occurs.
    102     */
    103    @Override
    104    public long skip(final long n) throws IOException {
    105        if (n < 0L) {
    106            throw new IllegalArgumentException("skip value < 0");
    107        }
    108        final int bufSize = (int) Math.min(n, MAX_SKIP_BUFFER_SIZE);
    109        if (skipBuffer == null || skipBuffer.length < bufSize) {
    110            skipBuffer = new char[bufSize];
    111        }
    112        long remaining = n;
    113        while (remaining > 0) {
    114            final int countOrEof = read(skipBuffer, 0, (int) Math.min(remaining, bufSize));
    115            if (countOrEof == EOF) {
    116                break;
    117            }
    118            remaining -= countOrEof;
    119        }
    120        return n - remaining;
    121    }
    122}