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;
023
024/**
025 * {@link Reader} implementation that can read from String, StringBuffer,
026 * StringBuilder or CharBuffer.
027 * <p>
028 * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
029 *
030 * @since 1.4
031 */
032public class CharSequenceReader extends Reader implements Serializable {
033
034    private static final long serialVersionUID = 3724187752191401220L;
035    private final CharSequence charSequence;
036    private int idx;
037    private int mark;
038
039    /**
040     * Construct a new instance with the specified character sequence.
041     *
042     * @param charSequence The character sequence, may be {@code null}
043     */
044    public CharSequenceReader(final CharSequence charSequence) {
045        this.charSequence = charSequence != null ? charSequence : "";
046    }
047
048    /**
049     * Close resets the file back to the start and removes any marked position.
050     */
051    @Override
052    public void close() {
053        idx = 0;
054        mark = 0;
055    }
056
057    /**
058     * Mark the current position.
059     *
060     * @param readAheadLimit ignored
061     */
062    @Override
063    public void mark(final int readAheadLimit) {
064        mark = idx;
065    }
066
067    /**
068     * Mark is supported (returns true).
069     *
070     * @return {@code true}
071     */
072    @Override
073    public boolean markSupported() {
074        return true;
075    }
076
077    /**
078     * Read a single character.
079     *
080     * @return the next character from the character sequence
081     * or -1 if the end has been reached.
082     */
083    @Override
084    public int read() {
085        if (idx >= charSequence.length()) {
086            return EOF;
087        } else {
088            return charSequence.charAt(idx++);
089        }
090    }
091
092    /**
093     * Read the specified number of characters into the array.
094     *
095     * @param array The array to store the characters in
096     * @param offset The starting position in the array to store
097     * @param length The maximum number of characters to read
098     * @return The number of characters read or -1 if there are
099     * no more
100     */
101    @Override
102    public int read(final char[] array, final int offset, final int length) {
103        if (idx >= charSequence.length()) {
104            return EOF;
105        }
106        if (array == null) {
107            throw new NullPointerException("Character array is missing");
108        }
109        if (length < 0 || offset < 0 || offset + length > array.length) {
110            throw new IndexOutOfBoundsException("Array Size=" + array.length +
111                    ", offset=" + offset + ", length=" + length);
112        }
113        int count = 0;
114        for (int i = 0; i < length; i++) {
115            final int c = read();
116            if (c == EOF) {
117                return count;
118            }
119            array[offset + i] = (char)c;
120            count++;
121        }
122        return count;
123    }
124
125    /**
126     * Reset the reader to the last marked position (or the beginning if
127     * mark has not been called).
128     */
129    @Override
130    public void reset() {
131        idx = mark;
132    }
133
134    /**
135     * Skip the specified number of characters.
136     *
137     * @param n The number of characters to skip
138     * @return The actual number of characters skipped
139     */
140    @Override
141    public long skip(final long n) {
142        if (n < 0) {
143            throw new IllegalArgumentException(
144                    "Number of characters to skip is less than zero: " + n);
145        }
146        if (idx >= charSequence.length()) {
147            return EOF;
148        }
149        final int dest = (int)Math.min(charSequence.length(), idx + n);
150        final int count = dest - idx;
151        idx = dest;
152        return count;
153    }
154
155    /**
156     * Return a String representation of the underlying
157     * character sequence.
158     *
159     * @return The contents of the character sequence
160     */
161    @Override
162    public String toString() {
163        return charSequence.toString();
164    }
165}