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 */
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     * Checks if this instance is closed and throws an IOException if so.
051     *
052     * @throws IOException if this instance is closed.
053     */
054    void checkOpen() throws IOException {
055        Input.checkOpen(!isClosed());
056    }
057
058    @Override
059    public void close() throws IOException {
060        closed = true;
061    }
062
063    /**
064     * Tests whether this instance is closed; if {@link #close()} completed successfully.
065     *
066     * @return whether this instance is closed.
067     */
068    public boolean isClosed() {
069        return closed;
070    }
071
072    /**
073     * Sets whether this instance is closed.
074     *
075     * @param closed whether this instance is closed.
076     */
077    public void setClosed(final boolean closed) {
078        this.closed = closed;
079    }
080
081    /**
082     * Skips characters by reading from this instance.
083     *
084     * This method will <em>block</em> until:
085     * <ul>
086     * <li>some characters are available,</li>
087     * <li>an I/O error occurs, or</li>
088     * <li>the end of the stream is reached.</li>
089     * </ul>
090     *
091     * @param n The number of characters to skip.
092     * @return The number of characters actually skipped.
093     * @throws IllegalArgumentException If {@code n} is negative.
094     * @throws IOException              If an I/O error occurs.
095     */
096    @Override
097    public long skip(final long n) throws IOException {
098        if (n < 0L) {
099            throw new IllegalArgumentException("skip value < 0");
100        }
101        final int bufSize = (int) Math.min(n, MAX_SKIP_BUFFER_SIZE);
102        if (skipBuffer == null || skipBuffer.length < bufSize) {
103            skipBuffer = new char[bufSize];
104        }
105        long remaining = n;
106        while (remaining > 0) {
107            final int countOrEof = read(skipBuffer, 0, (int) Math.min(remaining, bufSize));
108            if (countOrEof == EOF) {
109                break;
110            }
111            remaining -= countOrEof;
112        }
113        return n - remaining;
114    }
115}