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