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