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 up to {@code count} characters from this CharArrayReader and
188     * stores them at {@code offset} in the character array {@code buffer}.
189     * Returns the number of characters actually read or -1 if the end of reader
190     * was encountered.
191     *
192     * @throws IndexOutOfBoundsException
193     * if {@code offset < 0 || count < 0 || offset + count > buffer.length}.
194     * @throws IOException
195     *             if this reader is closed.
196     */
197    @Override
198    public int read(char[] buffer, int offset, int count) throws IOException {
199        Arrays.checkOffsetAndCount(buffer.length, offset, count);
200        synchronized (lock) {
201            checkNotClosed();
202            if (pos < this.count) {
203                int bytesRead = pos + count > this.count ? this.count - pos : count;
204                System.arraycopy(this.buf, pos, buffer, offset, bytesRead);
205                pos += bytesRead;
206                return bytesRead;
207            }
208            return -1;
209        }
210    }
211
212    /**
213     * Indicates whether this reader is ready to be read without blocking.
214     * Returns {@code true} if the next {@code read} will not block. Returns
215     * {@code false} if this reader may or may not block when {@code read} is
216     * called. The implementation in CharArrayReader always returns {@code true}
217     * even when it has been closed.
218     *
219     * @return {@code true} if this reader will not block when {@code read} is
220     *         called, {@code false} if unknown or blocking will occur.
221     * @throws IOException
222     *             if this reader is closed.
223     */
224    @Override
225    public boolean ready() throws IOException {
226        synchronized (lock) {
227            checkNotClosed();
228            return pos != count;
229        }
230    }
231
232    /**
233     * Resets this reader's position to the last {@code mark()} location.
234     * Invocations of {@code read()} and {@code skip()} will occur from this new
235     * location. If this reader has not been marked, it is reset to the
236     * beginning of the string.
237     *
238     * @throws IOException
239     *             if this reader is closed.
240     */
241    @Override
242    public void reset() throws IOException {
243        synchronized (lock) {
244            checkNotClosed();
245            pos = markedPos != -1 ? markedPos : 0;
246        }
247    }
248
249    /**
250     * Skips {@code charCount} characters in this reader. Subsequent calls to
251     * {@code read} will not return these characters unless {@code reset}
252     * is used. This method does nothing and returns 0 if {@code charCount <= 0}.
253     *
254     * @return the number of characters actually skipped.
255     * @throws IOException
256     *             if this reader is closed.
257     */
258    @Override
259    public long skip(long charCount) throws IOException {
260        synchronized (lock) {
261            checkNotClosed();
262            if (charCount <= 0) {
263                return 0;
264            }
265            long skipped = 0;
266            if (charCount < this.count - pos) {
267                pos = pos + (int) charCount;
268                skipped = charCount;
269            } else {
270                skipped = this.count - pos;
271                pos = this.count;
272            }
273            return skipped;
274        }
275    }
276}
277