001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input; 018 019import static org.apache.commons.io.IOUtils.EOF; 020 021import java.io.Reader; 022import java.io.Serializable; 023import java.util.Objects; 024 025/** 026 * {@link Reader} implementation that can read from String, StringBuffer, 027 * StringBuilder or CharBuffer. 028 * <p> 029 * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}. 030 * </p> 031 * <h2>Deprecating Serialization</h2> 032 * <p> 033 * <em>Serialization is deprecated and will be removed in 3.0.</em> 034 * </p> 035 * 036 * @since 1.4 037 */ 038public class CharSequenceReader extends Reader implements Serializable { 039 040 private static final long serialVersionUID = 3724187752191401220L; 041 042 /** Source for reading. */ 043 private final CharSequence charSequence; 044 045 /** Reading index. */ 046 private int idx; 047 048 /** Reader mark. */ 049 private int mark; 050 051 /** 052 * The start index in the character sequence, inclusive. 053 * <p> 054 * When de-serializing a CharSequenceReader that was serialized before 055 * this fields was added, this field will be initialized to 0, which 056 * gives the same behavior as before: start reading from the start. 057 * </p> 058 * 059 * @see #start() 060 * @since 2.7 061 */ 062 private final int start; 063 064 /** 065 * The end index in the character sequence, exclusive. 066 * <p> 067 * When de-serializing a CharSequenceReader that was serialized before 068 * this fields was added, this field will be initialized to {@code null}, 069 * which gives the same behavior as before: stop reading at the 070 * CharSequence's length. 071 * If this field was an int instead, it would be initialized to 0 when the 072 * CharSequenceReader is de-serialized, causing it to not return any 073 * characters at all. 074 * </p> 075 * 076 * @see #end() 077 * @since 2.7 078 */ 079 private final Integer end; 080 081 /** 082 * Constructs a new instance with the specified character sequence. 083 * 084 * @param charSequence The character sequence, may be {@code null} 085 */ 086 public CharSequenceReader(final CharSequence charSequence) { 087 this(charSequence, 0); 088 } 089 090 /** 091 * Constructs a new instance with a portion of the specified character sequence. 092 * <p> 093 * The start index is not strictly enforced to be within the bounds of the 094 * character sequence. This allows the character sequence to grow or shrink 095 * in size without risking any {@link IndexOutOfBoundsException} to be thrown. 096 * Instead, if the character sequence grows smaller than the start index, this 097 * instance will act as if all characters have been read. 098 * </p> 099 * 100 * @param charSequence The character sequence, may be {@code null} 101 * @param start The start index in the character sequence, inclusive 102 * @throws IllegalArgumentException if the start index is negative 103 * @since 2.7 104 */ 105 public CharSequenceReader(final CharSequence charSequence, final int start) { 106 this(charSequence, start, Integer.MAX_VALUE); 107 } 108 109 /** 110 * Constructs a new instance with a portion of the specified character sequence. 111 * <p> 112 * The start and end indexes are not strictly enforced to be within the bounds 113 * of the character sequence. This allows the character sequence to grow or shrink 114 * in size without risking any {@link IndexOutOfBoundsException} to be thrown. 115 * Instead, if the character sequence grows smaller than the start index, this 116 * instance will act as if all characters have been read; if the character sequence 117 * grows smaller than the end, this instance will use the actual character sequence 118 * length. 119 * </p> 120 * 121 * @param charSequence The character sequence, may be {@code null} 122 * @param start The start index in the character sequence, inclusive 123 * @param end The end index in the character sequence, exclusive 124 * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index 125 * @since 2.7 126 */ 127 public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { 128 if (start < 0) { 129 throw new IllegalArgumentException("Start index is less than zero: " + start); 130 } 131 if (end < start) { 132 throw new IllegalArgumentException("End index is less than start " + start + ": " + end); 133 } 134 // Don't check the start and end indexes against the CharSequence, 135 // to let it grow and shrink without breaking existing behavior. 136 137 this.charSequence = charSequence != null ? charSequence : ""; 138 this.start = start; 139 this.end = end; 140 141 this.idx = start; 142 this.mark = start; 143 } 144 145 /** 146 * Close resets the file back to the start and removes any marked position. 147 */ 148 @Override 149 public void close() { 150 idx = start; 151 mark = start; 152 } 153 154 /** 155 * Returns the index in the character sequence to end reading at, taking into account its length. 156 * 157 * @return The end index in the character sequence (exclusive). 158 */ 159 private int end() { 160 /* 161 * end == null for de-serialized instances that were serialized before start and end were added. 162 * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. 163 */ 164 return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); 165 } 166 167 /** 168 * Mark the current position. 169 * 170 * @param readAheadLimit ignored 171 */ 172 @Override 173 public void mark(final int readAheadLimit) { 174 mark = idx; 175 } 176 177 /** 178 * Mark is supported (returns true). 179 * 180 * @return {@code true} 181 */ 182 @Override 183 public boolean markSupported() { 184 return true; 185 } 186 187 /** 188 * Read a single character. 189 * 190 * @return the next character from the character sequence 191 * or -1 if the end has been reached. 192 */ 193 @Override 194 public int read() { 195 if (idx >= end()) { 196 return EOF; 197 } 198 return charSequence.charAt(idx++); 199 } 200 201 /** 202 * Read the specified number of characters into the array. 203 * 204 * @param array The array to store the characters in 205 * @param offset The starting position in the array to store 206 * @param length The maximum number of characters to read 207 * @return The number of characters read or -1 if there are 208 * no more 209 */ 210 @Override 211 public int read(final char[] array, final int offset, final int length) { 212 if (idx >= end()) { 213 return EOF; 214 } 215 Objects.requireNonNull(array, "array"); 216 if (length < 0 || offset < 0 || offset + length > array.length) { 217 throw new IndexOutOfBoundsException("Array Size=" + array.length + 218 ", offset=" + offset + ", length=" + length); 219 } 220 221 if (charSequence instanceof String) { 222 final int count = Math.min(length, end() - idx); 223 ((String) charSequence).getChars(idx, idx + count, array, offset); 224 idx += count; 225 return count; 226 } 227 if (charSequence instanceof StringBuilder) { 228 final int count = Math.min(length, end() - idx); 229 ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); 230 idx += count; 231 return count; 232 } 233 if (charSequence instanceof StringBuffer) { 234 final int count = Math.min(length, end() - idx); 235 ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); 236 idx += count; 237 return count; 238 } 239 240 int count = 0; 241 for (int i = 0; i < length; i++) { 242 final int c = read(); 243 if (c == EOF) { 244 return count; 245 } 246 array[offset + i] = (char) c; 247 count++; 248 } 249 return count; 250 } 251 252 /** 253 * Tells whether this stream is ready to be read. 254 * 255 * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise. 256 */ 257 @Override 258 public boolean ready() { 259 return idx < end(); 260 } 261 262 /** 263 * Reset the reader to the last marked position (or the beginning if 264 * mark has not been called). 265 */ 266 @Override 267 public void reset() { 268 idx = mark; 269 } 270 271 /** 272 * Skip the specified number of characters. 273 * 274 * @param n The number of characters to skip 275 * @return The actual number of characters skipped 276 */ 277 @Override 278 public long skip(final long n) { 279 if (n < 0) { 280 throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n); 281 } 282 if (idx >= end()) { 283 return 0; 284 } 285 final int dest = (int) Math.min(end(), idx + n); 286 final int count = dest - idx; 287 idx = dest; 288 return count; 289 } 290 291 /** 292 * Returns the index in the character sequence to start reading from, taking into account its length. 293 * 294 * @return The start index in the character sequence (inclusive). 295 */ 296 private int start() { 297 return Math.min(charSequence.length(), start); 298 } 299 300 /** 301 * Gets a String representation of the underlying 302 * character sequence. 303 * 304 * @return The contents of the character sequence 305 */ 306 @Override 307 public String toString() { 308 return charSequence.subSequence(start(), end()).toString(); 309 } 310}