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}