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