1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.io.input;
19
20 import static org.apache.commons.io.IOUtils.EOF;
21
22 import java.io.IOException;
23 import java.io.Reader;
24
25 import org.apache.commons.io.IOUtils;
26
27 /**
28 * A {@link Reader} without any of the superclass' synchronization.
29 *
30 * @since 2.17.0
31 */
32 public abstract class UnsynchronizedReader extends Reader {
33
34 /**
35 * The maximum skip-buffer size.
36 */
37 private static final int MAX_SKIP_BUFFER_SIZE = IOUtils.DEFAULT_BUFFER_SIZE;
38
39 /**
40 * Whether {@link #close()} completed successfully.
41 */
42 private boolean closed;
43
44 /**
45 * The skip buffer, defaults to null until allocated in {@link UnsynchronizedReader#skip(long)}.
46 */
47 private char skipBuffer[];
48
49 /**
50 * Constructs a new instance.
51 */
52 public UnsynchronizedReader() {
53 // empty
54 }
55
56 /**
57 * Checks if this instance is closed and throws an IOException if so.
58 *
59 * @throws IOException if this instance is closed.
60 */
61 void checkOpen() throws IOException {
62 Input.checkOpen(!isClosed());
63 }
64
65 @Override
66 public void close() throws IOException {
67 closed = true;
68 }
69
70 /**
71 * Tests whether this instance is closed; if {@link #close()} completed successfully.
72 *
73 * @return whether this instance is closed.
74 */
75 public boolean isClosed() {
76 return closed;
77 }
78
79 /**
80 * Sets whether this instance is closed.
81 *
82 * @param closed whether this instance is closed.
83 */
84 public void setClosed(final boolean closed) {
85 this.closed = closed;
86 }
87
88 /**
89 * Skips characters by reading from this instance.
90 *
91 * This method will <em>block</em> until:
92 * <ul>
93 * <li>some characters are available,</li>
94 * <li>an I/O error occurs, or</li>
95 * <li>the end of the stream is reached.</li>
96 * </ul>
97 *
98 * @param n The number of characters to skip.
99 * @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 }