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     * 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}