CharSequenceReader.java

  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. package org.apache.commons.io.input;

  18. import static org.apache.commons.io.IOUtils.EOF;

  19. import java.io.Reader;
  20. import java.io.Serializable;
  21. import java.util.Objects;

  22. /**
  23.  * {@link Reader} implementation that can read from String, StringBuffer,
  24.  * StringBuilder or CharBuffer.
  25.  * <p>
  26.  * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
  27.  * </p>
  28.  * <h2>Deprecating Serialization</h2>
  29.  * <p>
  30.  * <em>Serialization is deprecated and will be removed in 3.0.</em>
  31.  * </p>
  32.  *
  33.  * @since 1.4
  34.  */
  35. public class CharSequenceReader extends Reader implements Serializable {

  36.     private static final long serialVersionUID = 3724187752191401220L;

  37.     /** Source for reading. */
  38.     private final CharSequence charSequence;

  39.     /** Reading index. */
  40.     private int idx;

  41.     /** Reader mark. */
  42.     private int mark;

  43.     /**
  44.      * The start index in the character sequence, inclusive.
  45.      * <p>
  46.      * When de-serializing a CharSequenceReader that was serialized before
  47.      * this fields was added, this field will be initialized to 0, which
  48.      * gives the same behavior as before: start reading from the start.
  49.      * </p>
  50.      *
  51.      * @see #start()
  52.      * @since 2.7
  53.      */
  54.     private final int start;

  55.     /**
  56.      * The end index in the character sequence, exclusive.
  57.      * <p>
  58.      * When de-serializing a CharSequenceReader that was serialized before
  59.      * this fields was added, this field will be initialized to {@code null},
  60.      * which gives the same behavior as before: stop reading at the
  61.      * CharSequence's length.
  62.      * If this field was an int instead, it would be initialized to 0 when the
  63.      * CharSequenceReader is de-serialized, causing it to not return any
  64.      * characters at all.
  65.      * </p>
  66.      *
  67.      * @see #end()
  68.      * @since 2.7
  69.      */
  70.     private final Integer end;

  71.     /**
  72.      * Constructs a new instance with the specified character sequence.
  73.      *
  74.      * @param charSequence The character sequence, may be {@code null}
  75.      */
  76.     public CharSequenceReader(final CharSequence charSequence) {
  77.         this(charSequence, 0);
  78.     }

  79.     /**
  80.      * Constructs a new instance with a portion of the specified character sequence.
  81.      * <p>
  82.      * The start index is not strictly enforced to be within the bounds of the
  83.      * character sequence. This allows the character sequence to grow or shrink
  84.      * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
  85.      * Instead, if the character sequence grows smaller than the start index, this
  86.      * instance will act as if all characters have been read.
  87.      * </p>
  88.      *
  89.      * @param charSequence The character sequence, may be {@code null}
  90.      * @param start The start index in the character sequence, inclusive
  91.      * @throws IllegalArgumentException if the start index is negative
  92.      * @since 2.7
  93.      */
  94.     public CharSequenceReader(final CharSequence charSequence, final int start) {
  95.         this(charSequence, start, Integer.MAX_VALUE);
  96.     }

  97.     /**
  98.      * Constructs a new instance with a portion of the specified character sequence.
  99.      * <p>
  100.      * The start and end indexes are not strictly enforced to be within the bounds
  101.      * of the character sequence. This allows the character sequence to grow or shrink
  102.      * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
  103.      * Instead, if the character sequence grows smaller than the start index, this
  104.      * instance will act as if all characters have been read; if the character sequence
  105.      * grows smaller than the end, this instance will use the actual character sequence
  106.      * length.
  107.      * </p>
  108.      *
  109.      * @param charSequence The character sequence, may be {@code null}
  110.      * @param start The start index in the character sequence, inclusive
  111.      * @param end The end index in the character sequence, exclusive
  112.      * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index
  113.      * @since 2.7
  114.      */
  115.     public CharSequenceReader(final CharSequence charSequence, final int start, final int end) {
  116.         if (start < 0) {
  117.             throw new IllegalArgumentException("Start index is less than zero: " + start);
  118.         }
  119.         if (end < start) {
  120.             throw new IllegalArgumentException("End index is less than start " + start + ": " + end);
  121.         }
  122.         // Don't check the start and end indexes against the CharSequence,
  123.         // to let it grow and shrink without breaking existing behavior.

  124.         this.charSequence = charSequence != null ? charSequence : "";
  125.         this.start = start;
  126.         this.end = end;

  127.         this.idx = start;
  128.         this.mark = start;
  129.     }

  130.     /**
  131.      * Close resets the file back to the start and removes any marked position.
  132.      */
  133.     @Override
  134.     public void close() {
  135.         idx = start;
  136.         mark = start;
  137.     }

  138.     /**
  139.      * Returns the index in the character sequence to end reading at, taking into account its length.
  140.      *
  141.      * @return The end index in the character sequence (exclusive).
  142.      */
  143.     private int end() {
  144.         /*
  145.          * end == null for de-serialized instances that were serialized before start and end were added.
  146.          * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence.
  147.          */
  148.         return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end);
  149.     }

  150.     /**
  151.      * Mark the current position.
  152.      *
  153.      * @param readAheadLimit ignored
  154.      */
  155.     @Override
  156.     public void mark(final int readAheadLimit) {
  157.         mark = idx;
  158.     }

  159.     /**
  160.      * Mark is supported (returns true).
  161.      *
  162.      * @return {@code true}
  163.      */
  164.     @Override
  165.     public boolean markSupported() {
  166.         return true;
  167.     }

  168.     /**
  169.      * Read a single character.
  170.      *
  171.      * @return the next character from the character sequence
  172.      * or -1 if the end has been reached.
  173.      */
  174.     @Override
  175.     public int read() {
  176.         if (idx >= end()) {
  177.             return EOF;
  178.         }
  179.         return charSequence.charAt(idx++);
  180.     }

  181.     /**
  182.      * Read the specified number of characters into the array.
  183.      *
  184.      * @param array The array to store the characters in
  185.      * @param offset The starting position in the array to store
  186.      * @param length The maximum number of characters to read
  187.      * @return The number of characters read or -1 if there are
  188.      * no more
  189.      */
  190.     @Override
  191.     public int read(final char[] array, final int offset, final int length) {
  192.         if (idx >= end()) {
  193.             return EOF;
  194.         }
  195.         Objects.requireNonNull(array, "array");
  196.         if (length < 0 || offset < 0 || offset + length > array.length) {
  197.             throw new IndexOutOfBoundsException("Array Size=" + array.length +
  198.                     ", offset=" + offset + ", length=" + length);
  199.         }

  200.         if (charSequence instanceof String) {
  201.             final int count = Math.min(length, end() - idx);
  202.             ((String) charSequence).getChars(idx, idx + count, array, offset);
  203.             idx += count;
  204.             return count;
  205.         }
  206.         if (charSequence instanceof StringBuilder) {
  207.             final int count = Math.min(length, end() - idx);
  208.             ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset);
  209.             idx += count;
  210.             return count;
  211.         }
  212.         if (charSequence instanceof StringBuffer) {
  213.             final int count = Math.min(length, end() - idx);
  214.             ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset);
  215.             idx += count;
  216.             return count;
  217.         }

  218.         int count = 0;
  219.         for (int i = 0; i < length; i++) {
  220.             final int c = read();
  221.             if (c == EOF) {
  222.                 return count;
  223.             }
  224.             array[offset + i] = (char) c;
  225.             count++;
  226.         }
  227.         return count;
  228.     }

  229.     /**
  230.      * Tells whether this stream is ready to be read.
  231.      *
  232.      * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise.
  233.      */
  234.     @Override
  235.     public boolean ready() {
  236.         return idx < end();
  237.     }

  238.     /**
  239.      * Reset the reader to the last marked position (or the beginning if
  240.      * mark has not been called).
  241.      */
  242.     @Override
  243.     public void reset() {
  244.         idx = mark;
  245.     }

  246.     /**
  247.      * Skip the specified number of characters.
  248.      *
  249.      * @param n The number of characters to skip
  250.      * @return The actual number of characters skipped
  251.      */
  252.     @Override
  253.     public long skip(final long n) {
  254.         if (n < 0) {
  255.             throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n);
  256.         }
  257.         if (idx >= end()) {
  258.             return 0;
  259.         }
  260.         final int dest = (int) Math.min(end(), idx + n);
  261.         final int count = dest - idx;
  262.         idx = dest;
  263.         return count;
  264.     }

  265.     /**
  266.      * Returns the index in the character sequence to start reading from, taking into account its length.
  267.      *
  268.      * @return The start index in the character sequence (inclusive).
  269.      */
  270.     private int start() {
  271.         return Math.min(charSequence.length(), start);
  272.     }

  273.     /**
  274.      * Gets a String representation of the underlying
  275.      * character sequence.
  276.      *
  277.      * @return The contents of the character sequence
  278.      */
  279.     @Override
  280.     public String toString() {
  281.         return charSequence.subSequence(start(), end()).toString();
  282.     }
  283. }