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}