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
18package java.io;
19
20import java.util.Arrays;
21
22/**
23 * A specialized {@link Reader} that reads characters from a {@code String} in
24 * a sequential manner.
25 *
26 * @see StringWriter
27 */
28public class StringReader extends Reader {
29    private String str;
30
31    private int markpos = -1;
32
33    private int pos;
34
35    private int count;
36
37    /**
38     * Construct a new {@code StringReader} with {@code str} as source. The size
39     * of the reader is set to the {@code length()} of the string and the Object
40     * to synchronize access through is set to {@code str}.
41     *
42     * @param str
43     *            the source string for this reader.
44     */
45    public StringReader(String str) {
46        this.str = str;
47        this.count = str.length();
48    }
49
50    /**
51     * Closes this reader. Once it is closed, read operations on this reader
52     * will throw an {@code IOException}. Only the first invocation of this
53     * method has any effect.
54     */
55    @Override
56    public void close() {
57        str = null;
58    }
59
60    /**
61     * Returns a boolean indicating whether this reader is closed.
62     *
63     * @return {@code true} if closed, otherwise {@code false}.
64     */
65    private boolean isClosed() {
66        return str == null;
67    }
68
69    /**
70     * Sets a mark position in this reader. The parameter {@code readLimit} is
71     * ignored for this class. Calling {@code reset()} will reposition the
72     * reader back to the marked position.
73     *
74     * @param readLimit
75     *            ignored for {@code StringReader} instances.
76     * @throws IllegalArgumentException
77     *             if {@code readLimit < 0}.
78     * @throws IOException
79     *             if this reader is closed.
80     * @see #markSupported()
81     * @see #reset()
82     */
83    @Override
84    public void mark(int readLimit) throws IOException {
85        if (readLimit < 0) {
86            throw new IllegalArgumentException();
87        }
88
89        synchronized (lock) {
90            checkNotClosed();
91            markpos = pos;
92        }
93    }
94
95    private void checkNotClosed() throws IOException {
96        if (isClosed()) {
97            throw new IOException("StringReader is closed");
98        }
99    }
100
101    /**
102     * Indicates whether this reader supports the {@code mark()} and {@code
103     * reset()} methods. This implementation returns {@code true}.
104     *
105     * @return always {@code true}.
106     */
107    @Override
108    public boolean markSupported() {
109        return true;
110    }
111
112    /**
113     * Reads a single character from the source string and returns it as an
114     * integer with the two higher-order bytes set to 0. Returns -1 if the end
115     * of the source string has been reached.
116     *
117     * @return the character read or -1 if the end of the source string has been
118     *         reached.
119     * @throws IOException
120     *             if this reader is closed.
121     */
122    @Override
123    public int read() throws IOException {
124        synchronized (lock) {
125            checkNotClosed();
126            if (pos != count) {
127                return str.charAt(pos++);
128            }
129            return -1;
130        }
131    }
132
133    /**
134     * Reads at most {@code len} characters from the source string and stores
135     * them at {@code offset} in the character array {@code buf}. Returns the
136     * number of characters actually read or -1 if the end of the source string
137     * has been reached.
138     *
139     * @param buf
140     *            the character array to store the characters read.
141     * @param offset
142     *            the initial position in {@code buffer} to store the characters
143     *            read from this reader.
144     * @param len
145     *            the maximum number of characters to read.
146     * @return the number of characters read or -1 if the end of the reader has
147     *         been reached.
148     * @throws IndexOutOfBoundsException
149     *             if {@code offset < 0} or {@code len < 0}, or if
150     *             {@code offset + len} is greater than the size of {@code buf}.
151     * @throws IOException
152     *             if this reader is closed.
153     */
154    @Override
155    public int read(char[] buf, int offset, int len) throws IOException {
156        synchronized (lock) {
157            checkNotClosed();
158            Arrays.checkOffsetAndCount(buf.length, offset, len);
159            if (len == 0) {
160                return 0;
161            }
162            if (pos == this.count) {
163                return -1;
164            }
165            int end = pos + len > this.count ? this.count : pos + len;
166            str.getChars(pos, end, buf, offset);
167            int read = end - pos;
168            pos = end;
169            return read;
170        }
171    }
172
173    /**
174     * Indicates whether this reader is ready to be read without blocking. This
175     * implementation always returns {@code true}.
176     *
177     * @return always {@code true}.
178     * @throws IOException
179     *             if this reader is closed.
180     * @see #read()
181     * @see #read(char[], int, int)
182     */
183    @Override
184    public boolean ready() throws IOException {
185        synchronized (lock) {
186            checkNotClosed();
187            return true;
188        }
189    }
190
191    /**
192     * Resets this reader's position to the last {@code mark()} location.
193     * Invocations of {@code read()} and {@code skip()} will occur from this new
194     * location. If this reader has not been marked, it is reset to the
195     * beginning of the source string.
196     *
197     * @throws IOException
198     *             if this reader is closed.
199     * @see #mark(int)
200     * @see #markSupported()
201     */
202    @Override
203    public void reset() throws IOException {
204        synchronized (lock) {
205            checkNotClosed();
206            pos = markpos != -1 ? markpos : 0;
207        }
208    }
209
210    /**
211     * Moves {@code charCount} characters in the source string. Unlike the {@link
212     * Reader#skip(long) overridden method}, this method may skip negative skip
213     * distances: this rewinds the input so that characters may be read again.
214     * When the end of the source string has been reached, the input cannot be
215     * rewound.
216     *
217     * @param charCount
218     *            the maximum number of characters to skip. Positive values skip
219     *            forward; negative values skip backward.
220     * @return the number of characters actually skipped. This is bounded below
221     *            by the number of characters already read and above by the
222     *            number of characters remaining:<br> {@code -(num chars already
223     *            read) <= distance skipped <= num chars remaining}.
224     * @throws IOException
225     *             if this reader is closed.
226     * @see #mark(int)
227     * @see #markSupported()
228     * @see #reset()
229     */
230    @Override
231    public long skip(long charCount) throws IOException {
232        synchronized (lock) {
233            checkNotClosed();
234
235            int minSkip = -pos;
236            int maxSkip = count - pos;
237
238            if (maxSkip == 0 || charCount > maxSkip) {
239                charCount = maxSkip; // no rewinding if we're at the end
240            } else if (charCount < minSkip) {
241                charCount = minSkip;
242            }
243
244            pos += charCount;
245            return charCount;
246        }
247    }
248}
249