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 private final CharSequence charSequence; 042 private int idx; 043 private int mark; 044 045 /** 046 * The start index in the character sequence, inclusive. 047 * <p> 048 * When de-serializing a CharSequenceReader that was serialized before 049 * this fields was added, this field will be initialized to 0, which 050 * gives the same behavior as before: start reading from the start. 051 * </p> 052 * 053 * @see #start() 054 * @since 2.7 055 */ 056 private final int start; 057 058 /** 059 * The end index in the character sequence, exclusive. 060 * <p> 061 * When de-serializing a CharSequenceReader that was serialized before 062 * this fields was added, this field will be initialized to {@code null}, 063 * which gives the same behavior as before: stop reading at the 064 * CharSequence's length. 065 * If this field was an int instead, it would be initialized to 0 when the 066 * CharSequenceReader is de-serialized, causing it to not return any 067 * characters at all. 068 * </p> 069 * 070 * @see #end() 071 * @since 2.7 072 */ 073 private final Integer end; 074 075 /** 076 * Constructs a new instance with the specified character sequence. 077 * 078 * @param charSequence The character sequence, may be {@code null} 079 */ 080 public CharSequenceReader(final CharSequence charSequence) { 081 this(charSequence, 0); 082 } 083 084 /** 085 * Constructs a new instance with a portion of the specified character sequence. 086 * <p> 087 * The start index is not strictly enforced to be within the bounds of the 088 * character sequence. This allows the character sequence to grow or shrink 089 * in size without risking any {@link IndexOutOfBoundsException} to be thrown. 090 * Instead, if the character sequence grows smaller than the start index, this 091 * instance will act as if all characters have been read. 092 * </p> 093 * 094 * @param charSequence The character sequence, may be {@code null} 095 * @param start The start index in the character sequence, inclusive 096 * @throws IllegalArgumentException if the start index is negative 097 * @since 2.7 098 */ 099 public CharSequenceReader(final CharSequence charSequence, final int start) { 100 this(charSequence, start, Integer.MAX_VALUE); 101 } 102 103 /** 104 * Constructs a new instance with a portion of the specified character sequence. 105 * <p> 106 * The start and end indexes are not strictly enforced to be within the bounds 107 * of the character sequence. This allows the character sequence to grow or shrink 108 * in size without risking any {@link IndexOutOfBoundsException} to be thrown. 109 * Instead, if the character sequence grows smaller than the start index, this 110 * instance will act as if all characters have been read; if the character sequence 111 * grows smaller than the end, this instance will use the actual character sequence 112 * length. 113 * </p> 114 * 115 * @param charSequence The character sequence, may be {@code null} 116 * @param start The start index in the character sequence, inclusive 117 * @param end The end index in the character sequence, exclusive 118 * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index 119 * @since 2.7 120 */ 121 public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { 122 if (start < 0) { 123 throw new IllegalArgumentException("Start index is less than zero: " + start); 124 } 125 if (end < start) { 126 throw new IllegalArgumentException("End index is less than start " + start + ": " + end); 127 } 128 // Don't check the start and end indexes against the CharSequence, 129 // to let it grow and shrink without breaking existing behavior. 130 131 this.charSequence = charSequence != null ? charSequence : ""; 132 this.start = start; 133 this.end = end; 134 135 this.idx = start; 136 this.mark = start; 137 } 138 139 /** 140 * Close resets the file back to the start and removes any marked position. 141 */ 142 @Override 143 public void close() { 144 idx = start; 145 mark = start; 146 } 147 148 /** 149 * Returns the index in the character sequence to end reading at, taking into account its length. 150 * 151 * @return The end index in the character sequence (exclusive). 152 */ 153 private int end() { 154 /* 155 * end == null for de-serialized instances that were serialized before start and end were added. 156 * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. 157 */ 158 return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); 159 } 160 161 /** 162 * Mark the current position. 163 * 164 * @param readAheadLimit ignored 165 */ 166 @Override 167 public void mark(final int readAheadLimit) { 168 mark = idx; 169 } 170 171 /** 172 * Mark is supported (returns true). 173 * 174 * @return {@code true} 175 */ 176 @Override 177 public boolean markSupported() { 178 return true; 179 } 180 181 /** 182 * Read a single character. 183 * 184 * @return the next character from the character sequence 185 * or -1 if the end has been reached. 186 */ 187 @Override 188 public int read() { 189 if (idx >= end()) { 190 return EOF; 191 } 192 return charSequence.charAt(idx++); 193 } 194 195 /** 196 * Read the specified number of characters into the array. 197 * 198 * @param array The array to store the characters in 199 * @param offset The starting position in the array to store 200 * @param length The maximum number of characters to read 201 * @return The number of characters read or -1 if there are 202 * no more 203 */ 204 @Override 205 public int read(final char[] array, final int offset, final int length) { 206 if (idx >= end()) { 207 return EOF; 208 } 209 Objects.requireNonNull(array, "array"); 210 if (length < 0 || offset < 0 || offset + length > array.length) { 211 throw new IndexOutOfBoundsException("Array Size=" + array.length + 212 ", offset=" + offset + ", length=" + length); 213 } 214 215 if (charSequence instanceof String) { 216 final int count = Math.min(length, end() - idx); 217 ((String) charSequence).getChars(idx, idx + count, array, offset); 218 idx += count; 219 return count; 220 } 221 if (charSequence instanceof StringBuilder) { 222 final int count = Math.min(length, end() - idx); 223 ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); 224 idx += count; 225 return count; 226 } 227 if (charSequence instanceof StringBuffer) { 228 final int count = Math.min(length, end() - idx); 229 ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); 230 idx += count; 231 return count; 232 } 233 234 int count = 0; 235 for (int i = 0; i < length; i++) { 236 final int c = read(); 237 if (c == EOF) { 238 return count; 239 } 240 array[offset + i] = (char)c; 241 count++; 242 } 243 return count; 244 } 245 246 /** 247 * Tells whether this stream is ready to be read. 248 * 249 * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise. 250 */ 251 @Override 252 public boolean ready() { 253 return idx < end(); 254 } 255 256 /** 257 * Reset the reader to the last marked position (or the beginning if 258 * mark has not been called). 259 */ 260 @Override 261 public void reset() { 262 idx = mark; 263 } 264 265 /** 266 * Skip the specified number of characters. 267 * 268 * @param n The number of characters to skip 269 * @return The actual number of characters skipped 270 */ 271 @Override 272 public long skip(final long n) { 273 if (n < 0) { 274 throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n); 275 } 276 if (idx >= end()) { 277 return 0; 278 } 279 final int dest = (int) Math.min(end(), idx + n); 280 final int count = dest - idx; 281 idx = dest; 282 return count; 283 } 284 285 /** 286 * Returns the index in the character sequence to start reading from, taking into account its length. 287 * 288 * @return The start index in the character sequence (inclusive). 289 */ 290 private int start() { 291 return Math.min(charSequence.length(), start); 292 } 293 294 /** 295 * Return a String representation of the underlying 296 * character sequence. 297 * 298 * @return The contents of the character sequence 299 */ 300 @Override 301 public String toString() { 302 final CharSequence subSequence = charSequence.subSequence(start(), end()); 303 return subSequence.toString(); 304 } 305}