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("readLimit < 0: " + readLimit);
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 up to {@code count} characters from the source string and stores
135     * them at {@code offset} in the character array {@code buffer}. Returns the
136     * number of characters actually read or -1 if the end of the source string
137     * has been reached.
138     *
139     * @throws IndexOutOfBoundsException
140                if {@code offset < 0 || count < 0 || offset + count > buffer.length}.
141     * @throws IOException
142     *             if this reader is closed.
143     */
144    @Override
145    public int read(char[] buffer, int offset, int count) throws IOException {
146        synchronized (lock) {
147            checkNotClosed();
148            Arrays.checkOffsetAndCount(buffer.length, offset, count);
149            if (count == 0) {
150                return 0;
151            }
152            if (pos == this.count) {
153                return -1;
154            }
155            int end = pos + count > this.count ? this.count : pos + count;
156            str.getChars(pos, end, buffer, offset);
157            int read = end - pos;
158            pos = end;
159            return read;
160        }
161    }
162
163    /**
164     * Indicates whether this reader is ready to be read without blocking. This
165     * implementation always returns {@code true}.
166     *
167     * @return always {@code true}.
168     * @throws IOException
169     *             if this reader is closed.
170     * @see #read()
171     * @see #read(char[], int, int)
172     */
173    @Override
174    public boolean ready() throws IOException {
175        synchronized (lock) {
176            checkNotClosed();
177            return true;
178        }
179    }
180
181    /**
182     * Resets this reader's position to the last {@code mark()} location.
183     * Invocations of {@code read()} and {@code skip()} will occur from this new
184     * location. If this reader has not been marked, it is reset to the
185     * beginning of the source string.
186     *
187     * @throws IOException
188     *             if this reader is closed.
189     * @see #mark(int)
190     * @see #markSupported()
191     */
192    @Override
193    public void reset() throws IOException {
194        synchronized (lock) {
195            checkNotClosed();
196            pos = markpos != -1 ? markpos : 0;
197        }
198    }
199
200    /**
201     * Moves {@code charCount} characters in the source string. Unlike the {@link
202     * Reader#skip(long) overridden method}, this method may skip negative skip
203     * distances: this rewinds the input so that characters may be read again.
204     * When the end of the source string has been reached, the input cannot be
205     * rewound.
206     *
207     * @param charCount
208     *            the maximum number of characters to skip. Positive values skip
209     *            forward; negative values skip backward.
210     * @return the number of characters actually skipped. This is bounded below
211     *            by the number of characters already read and above by the
212     *            number of characters remaining:<br> {@code -(num chars already
213     *            read) <= distance skipped <= num chars remaining}.
214     * @throws IOException
215     *             if this reader is closed.
216     * @see #mark(int)
217     * @see #markSupported()
218     * @see #reset()
219     */
220    @Override
221    public long skip(long charCount) throws IOException {
222        synchronized (lock) {
223            checkNotClosed();
224
225            int minSkip = -pos;
226            int maxSkip = count - pos;
227
228            if (maxSkip == 0 || charCount > maxSkip) {
229                charCount = maxSkip; // no rewinding if we're at the end
230            } else if (charCount < minSkip) {
231                charCount = minSkip;
232            }
233
234            pos += charCount;
235            return charCount;
236        }
237    }
238}
239