View Javadoc
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    *      https://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  package org.apache.commons.io.input;
18  
19  import static org.apache.commons.io.IOUtils.EOF;
20  
21  import java.io.Reader;
22  import java.io.Serializable;
23  
24  import org.apache.commons.io.IOUtils;
25  
26  /**
27   * {@link Reader} implementation that can read from String, StringBuffer,
28   * StringBuilder or CharBuffer.
29   * <p>
30   * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
31   * </p>
32   * <h2>Deprecating Serialization</h2>
33   * <p>
34   * <em>Serialization is deprecated and will be removed in 3.0.</em>
35   * </p>
36   *
37   * @since 1.4
38   */
39  public class CharSequenceReader extends Reader implements Serializable {
40  
41      private static final long serialVersionUID = 3724187752191401220L;
42  
43      /** Source for reading. */
44      private final CharSequence charSequence;
45  
46      /** Reading index. */
47      private int idx;
48  
49      /** Reader mark. */
50      private int mark;
51  
52      /**
53       * The start index in the character sequence, inclusive.
54       * <p>
55       * When de-serializing a CharSequenceReader that was serialized before
56       * this fields was added, this field will be initialized to 0, which
57       * gives the same behavior as before: start reading from the start.
58       * </p>
59       *
60       * @see #start()
61       * @since 2.7
62       */
63      private final int start;
64  
65      /**
66       * The end index in the character sequence, exclusive.
67       * <p>
68       * When de-serializing a CharSequenceReader that was serialized before
69       * this fields was added, this field will be initialized to {@code null},
70       * which gives the same behavior as before: stop reading at the
71       * CharSequence's length.
72       * If this field was an int instead, it would be initialized to 0 when the
73       * CharSequenceReader is de-serialized, causing it to not return any
74       * characters at all.
75       * </p>
76       *
77       * @see #end()
78       * @since 2.7
79       */
80      private final Integer end;
81  
82      /**
83       * Constructs a new instance with the specified character sequence.
84       *
85       * @param charSequence The character sequence, may be {@code null}
86       */
87      public CharSequenceReader(final CharSequence charSequence) {
88          this(charSequence, 0);
89      }
90  
91      /**
92       * Constructs a new instance with a portion of the specified character sequence.
93       * <p>
94       * The start index is not strictly enforced to be within the bounds of the
95       * character sequence. This allows the character sequence to grow or shrink
96       * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
97       * Instead, if the character sequence grows smaller than the start index, this
98       * instance will act as if all characters have been read.
99       * </p>
100      *
101      * @param charSequence The character sequence, may be {@code null}
102      * @param start The start index in the character sequence, inclusive
103      * @throws IllegalArgumentException if the start index is negative
104      * @since 2.7
105      */
106     public CharSequenceReader(final CharSequence charSequence, final int start) {
107         this(charSequence, start, Integer.MAX_VALUE);
108     }
109 
110     /**
111      * Constructs a new instance with a portion of the specified character sequence.
112      * <p>
113      * The start and end indexes are not strictly enforced to be within the bounds
114      * of the character sequence. This allows the character sequence to grow or shrink
115      * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
116      * Instead, if the character sequence grows smaller than the start index, this
117      * instance will act as if all characters have been read; if the character sequence
118      * grows smaller than the end, this instance will use the actual character sequence
119      * length.
120      * </p>
121      *
122      * @param charSequence The character sequence, may be {@code null}
123      * @param start The start index in the character sequence, inclusive
124      * @param end The end index in the character sequence, exclusive
125      * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index
126      * @since 2.7
127      */
128     public CharSequenceReader(final CharSequence charSequence, final int start, final int end) {
129         if (start < 0) {
130             throw new IllegalArgumentException("Start index is less than zero: " + start);
131         }
132         if (end < start) {
133             throw new IllegalArgumentException("End index is less than start " + start + ": " + end);
134         }
135         // Don't check the start and end indexes against the CharSequence,
136         // to let it grow and shrink without breaking existing behavior.
137 
138         this.charSequence = charSequence != null ? charSequence : "";
139         this.start = start;
140         this.end = end;
141 
142         this.idx = start;
143         this.mark = start;
144     }
145 
146     /**
147      * Close resets the file back to the start and removes any marked position.
148      */
149     @Override
150     public void close() {
151         idx = start;
152         mark = start;
153     }
154 
155     /**
156      * Returns the index in the character sequence to end reading at, taking into account its length.
157      *
158      * @return The end index in the character sequence (exclusive).
159      */
160     private int end() {
161         /*
162          * end == null for de-serialized instances that were serialized before start and end were added.
163          * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence.
164          */
165         return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end);
166     }
167 
168     /**
169      * Mark the current position.
170      *
171      * @param readAheadLimit ignored
172      */
173     @Override
174     public void mark(final int readAheadLimit) {
175         mark = idx;
176     }
177 
178     /**
179      * Mark is supported (returns true).
180      *
181      * @return {@code true}
182      */
183     @Override
184     public boolean markSupported() {
185         return true;
186     }
187 
188     /**
189      * Reads a single character.
190      *
191      * @return the next character from the character sequence
192      * or -1 if the end has been reached.
193      */
194     @Override
195     public int read() {
196         if (idx >= end()) {
197             return EOF;
198         }
199         return charSequence.charAt(idx++);
200     }
201 
202     /**
203      * Reads the specified number of characters into the array.
204      *
205      * @param array The array to store the characters in
206      * @param offset The starting position in the array to store
207      * @param length The maximum number of characters to read
208      * @return The number of characters read or -1 if there are no more
209      * @throws NullPointerException if the array is {@code null}.
210      * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code array.length}.
211      */
212     @Override
213     public int read(final char[] array, final int offset, final int length) {
214         IOUtils.checkFromIndexSize(array, offset, length);
215         if (length == 0) {
216             return 0;
217         }
218         if (idx >= end()) {
219             return EOF;
220         }
221 
222         if (charSequence instanceof String) {
223             final int count = Math.min(length, end() - idx);
224             ((String) charSequence).getChars(idx, idx + count, array, offset);
225             idx += count;
226             return count;
227         }
228         if (charSequence instanceof StringBuilder) {
229             final int count = Math.min(length, end() - idx);
230             ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset);
231             idx += count;
232             return count;
233         }
234         if (charSequence instanceof StringBuffer) {
235             final int count = Math.min(length, end() - idx);
236             ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset);
237             idx += count;
238             return count;
239         }
240 
241         int count = 0;
242         for (int i = 0; i < length; i++) {
243             final int c = read();
244             if (c == EOF) {
245                 return count;
246             }
247             array[offset + i] = (char) c;
248             count++;
249         }
250         return count;
251     }
252 
253     /**
254      * Tells whether this stream is ready to be read.
255      *
256      * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise.
257      */
258     @Override
259     public boolean ready() {
260         return idx < end();
261     }
262 
263     /**
264      * Reset the reader to the last marked position (or the beginning if
265      * mark has not been called).
266      */
267     @Override
268     public void reset() {
269         idx = mark;
270     }
271 
272     /**
273      * Skip the specified number of characters.
274      *
275      * @param n The number of characters to skip
276      * @return The actual number of characters skipped
277      */
278     @Override
279     public long skip(final long n) {
280         if (n < 0) {
281             throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n);
282         }
283         if (idx >= end()) {
284             return 0;
285         }
286         final int dest = (int) Math.min(end(), idx + n);
287         final int count = dest - idx;
288         idx = dest;
289         return count;
290     }
291 
292     /**
293      * Returns the index in the character sequence to start reading from, taking into account its length.
294      *
295      * @return The start index in the character sequence (inclusive).
296      */
297     private int start() {
298         return Math.min(charSequence.length(), start);
299     }
300 
301     /**
302      * Gets a String representation of the underlying
303      * character sequence.
304      *
305      * @return The contents of the character sequence
306      */
307     @Override
308     public String toString() {
309         return charSequence.subSequence(start(), end()).toString();
310     }
311 }