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} for reading the contents of a char array.
24 *
25 * @see CharArrayWriter
26 */
27public class CharArrayReader extends Reader {
28    /**
29     * The buffer for characters.
30     */
31    protected char[] buf;
32
33    /**
34     * The current buffer position.
35     */
36    protected int pos;
37
38    /**
39     * The current mark position.
40     */
41    protected int markedPos = -1;
42
43    /**
44     * The ending index of the buffer.
45     */
46    protected int count;
47
48    /**
49     * Constructs a CharArrayReader on the char array {@code buf}. The size of
50     * the reader is set to the length of the buffer and the object to to read
51     * from is set to {@code buf}.
52     *
53     * @param buf
54     *            the char array from which to read.
55     */
56    public CharArrayReader(char[] buf) {
57        this.buf = buf;
58        this.count = buf.length;
59    }
60
61    /**
62     * Constructs a CharArrayReader on the char array {@code buf}. The size of
63     * the reader is set to {@code length} and the start position from which to
64     * read the buffer is set to {@code offset}.
65     *
66     * @param buf
67     *            the char array from which to read.
68     * @param offset
69     *            the index of the first character in {@code buf} to read.
70     * @param length
71     *            the number of characters that can be read from {@code buf}.
72     * @throws IllegalArgumentException
73     *             if {@code offset < 0} or {@code length < 0}, or if
74     *             {@code offset} is greater than the size of {@code buf} .
75     */
76    public CharArrayReader(char[] buf, int offset, int length) {
77        /*
78         * The spec of this constructor is broken. In defining the legal values
79         * of offset and length, it doesn't consider buffer's length. And to be
80         * compatible with the broken spec, we must also test whether
81         * (offset + length) overflows.
82         */
83        if (offset < 0 || offset > buf.length || length < 0 || offset + length < 0) {
84            throw new IllegalArgumentException();
85        }
86        this.buf = buf;
87        this.pos = offset;
88        this.markedPos = offset;
89
90        /* This is according to spec */
91        int bufferLength = buf.length;
92        this.count = offset + length < bufferLength ? length : bufferLength;
93    }
94
95    /**
96     * This method closes this CharArrayReader. Once it is closed, you can no
97     * longer read from it. Only the first invocation of this method has any
98     * effect.
99     */
100    @Override
101    public void close() {
102        synchronized (lock) {
103            if (isOpen()) {
104                buf = null;
105            }
106        }
107    }
108
109    /**
110     * Indicates whether this reader is open.
111     *
112     * @return {@code true} if the reader is open, {@code false} otherwise.
113     */
114    private boolean isOpen() {
115        return buf != null;
116    }
117
118    /**
119     * Indicates whether this reader is closed.
120     *
121     * @return {@code true} if the reader is closed, {@code false} otherwise.
122     */
123    private boolean isClosed() {
124        return buf == null;
125    }
126
127    /**
128     * Sets a mark position in this reader. The parameter {@code readLimit} is
129     * ignored for CharArrayReaders. Calling {@code reset()} will reposition the
130     * reader back to the marked position provided the mark has not been
131     * invalidated.
132     *
133     * @param readLimit
134     *            ignored for CharArrayReaders.
135     * @throws IOException
136     *             if this reader is closed.
137     */
138    @Override
139    public void mark(int readLimit) throws IOException {
140        synchronized (lock) {
141            checkNotClosed();
142            markedPos = pos;
143        }
144    }
145
146    private void checkNotClosed() throws IOException {
147        if (isClosed()) {
148            throw new IOException("CharArrayReader is closed");
149        }
150    }
151
152    /**
153     * Indicates whether this reader supports the {@code mark()} and
154     * {@code reset()} methods.
155     *
156     * @return {@code true} for CharArrayReader.
157     * @see #mark(int)
158     * @see #reset()
159     */
160    @Override
161    public boolean markSupported() {
162        return true;
163    }
164
165    /**
166     * Reads a single character from this reader and returns it as an integer
167     * with the two higher-order bytes set to 0. Returns -1 if no more
168     * characters are available from this reader.
169     *
170     * @return the character read as an int or -1 if the end of the reader has
171     *         been reached.
172     * @throws IOException
173     *             if this reader is closed.
174     */
175    @Override
176    public int read() throws IOException {
177        synchronized (lock) {
178            checkNotClosed();
179            if (pos == count) {
180                return -1;
181            }
182            return buf[pos++];
183        }
184    }
185
186    /**
187     * Reads at most {@code count} characters from this CharArrayReader and
188     * stores them at {@code offset} in the character array {@code buf}.
189     * Returns the number of characters actually read or -1 if the end of reader
190     * was encountered.
191     *
192     * @param buffer
193     *            the character array to store the characters read.
194     * @param offset
195     *            the initial position in {@code buffer} to store the characters
196     *            read from this reader.
197     * @param len
198     *            the maximum number of characters to read.
199     * @return number of characters read or -1 if the end of the reader has been
200     *         reached.
201     * @throws IndexOutOfBoundsException
202     *             if {@code offset < 0} or {@code len < 0}, or if
203     *             {@code offset + len} is bigger than the size of
204     *             {@code buffer}.
205     * @throws IOException
206     *             if this reader is closed.
207     */
208    @Override
209    public int read(char[] buffer, int offset, int len) throws IOException {
210        Arrays.checkOffsetAndCount(buffer.length, offset, len);
211        synchronized (lock) {
212            checkNotClosed();
213            if (pos < this.count) {
214                int bytesRead = pos + len > this.count ? this.count - pos : len;
215                System.arraycopy(this.buf, pos, buffer, offset, bytesRead);
216                pos += bytesRead;
217                return bytesRead;
218            }
219            return -1;
220        }
221    }
222
223    /**
224     * Indicates whether this reader is ready to be read without blocking.
225     * Returns {@code true} if the next {@code read} will not block. Returns
226     * {@code false} if this reader may or may not block when {@code read} is
227     * called. The implementation in CharArrayReader always returns {@code true}
228     * even when it has been closed.
229     *
230     * @return {@code true} if this reader will not block when {@code read} is
231     *         called, {@code false} if unknown or blocking will occur.
232     * @throws IOException
233     *             if this reader is closed.
234     */
235    @Override
236    public boolean ready() throws IOException {
237        synchronized (lock) {
238            checkNotClosed();
239            return pos != count;
240        }
241    }
242
243    /**
244     * Resets this reader's position to the last {@code mark()} location.
245     * Invocations of {@code read()} and {@code skip()} will occur from this new
246     * location. If this reader has not been marked, it is reset to the
247     * beginning of the string.
248     *
249     * @throws IOException
250     *             if this reader is closed.
251     */
252    @Override
253    public void reset() throws IOException {
254        synchronized (lock) {
255            checkNotClosed();
256            pos = markedPos != -1 ? markedPos : 0;
257        }
258    }
259
260    /**
261     * Skips {@code charCount} characters in this reader. Subsequent calls to
262     * {@code read} will not return these characters unless {@code reset}
263     * is used. This method does nothing and returns 0 if {@code charCount <= 0}.
264     *
265     * @return the number of characters actually skipped.
266     * @throws IOException
267     *             if this reader is closed.
268     */
269    @Override
270    public long skip(long charCount) throws IOException {
271        synchronized (lock) {
272            checkNotClosed();
273            if (charCount <= 0) {
274                return 0;
275            }
276            long skipped = 0;
277            if (charCount < this.count - pos) {
278                pos = pos + (int) charCount;
279                skipped = charCount;
280            } else {
281                skipped = this.count - pos;
282                pos = this.count;
283            }
284            return skipped;
285        }
286    }
287}
288