    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}