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 * https://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; 023 024import org.apache.commons.io.IOUtils; 025 026/** 027 * {@link Reader} implementation that can read from String, StringBuffer, 028 * StringBuilder or CharBuffer. 029 * <p> 030 * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}. 031 * </p> 032 * <h2>Deprecating Serialization</h2> 033 * <p> 034 * <em>Serialization is deprecated and will be removed in 3.0.</em> 035 * </p> 036 * 037 * @since 1.4 038 */ 039public class CharSequenceReader extends Reader implements Serializable { 040 041 private static final long serialVersionUID = 3724187752191401220L; 042 043 /** Source for reading. */ 044 private final CharSequence charSequence; 045 046 /** Reading index. */ 047 private int idx; 048 049 /** Reader mark. */ 050 private int mark; 051 052 /** 053 * The start index in the character sequence, inclusive. 054 * <p> 055 * When de-serializing a CharSequenceReader that was serialized before 056 * this fields was added, this field will be initialized to 0, which 057 * gives the same behavior as before: start reading from the start. 058 * </p> 059 * 060 * @see #start() 061 * @since 2.7 062 */ 063 private final int start; 064 065 /** 066 * The end index in the character sequence, exclusive. 067 * <p> 068 * When de-serializing a CharSequenceReader that was serialized before 069 * this fields was added, this field will be initialized to {@code null}, 070 * which gives the same behavior as before: stop reading at the 071 * CharSequence's length. 072 * If this field was an int instead, it would be initialized to 0 when the 073 * CharSequenceReader is de-serialized, causing it to not return any 074 * characters at all. 075 * </p> 076 * 077 * @see #end() 078 * @since 2.7 079 */ 080 private final Integer end; 081 082 /** 083 * Constructs a new instance with the specified character sequence. 084 * 085 * @param charSequence The character sequence, may be {@code null} 086 */ 087 public CharSequenceReader(final CharSequence charSequence) { 088 this(charSequence, 0); 089 } 090 091 /** 092 * Constructs a new instance with a portion of the specified character sequence. 093 * <p> 094 * The start index is not strictly enforced to be within the bounds of the 095 * character sequence. This allows the character sequence to grow or shrink 096 * in size without risking any {@link IndexOutOfBoundsException} to be thrown. 097 * Instead, if the character sequence grows smaller than the start index, this 098 * instance will act as if all characters have been read. 099 * </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}