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.IOException;
022import java.io.Reader;
023import java.util.Arrays;
024import java.util.Iterator;
025import java.util.Objects;
026
027/**
028 * Provides the contents of multiple Readers in sequence.
029 *
030 * @since 2.7
031 */
032public class SequenceReader extends Reader {
033
034    private Reader reader;
035    private Iterator<? extends Reader> readers;
036
037    /**
038     * Construct a new instance with readers
039     *
040     * @param readers the readers to read
041     */
042    public SequenceReader(final Iterable<? extends Reader> readers) {
043        this.readers = Objects.requireNonNull(readers, "readers").iterator();
044        this.reader = nextReader();
045    }
046
047    /**
048     * Construct a new instance with readers
049     *
050     * @param readers the readers to read
051     */
052    public SequenceReader(final Reader... readers) {
053        this(Arrays.asList(readers));
054    }
055
056    /*
057     * (non-Javadoc)
058     *
059     * @see java.io.Reader#close()
060     */
061    @Override
062    public void close() throws IOException {
063        this.readers = null;
064        this.reader = null;
065    }
066
067    /**
068     * Returns the next available reader or null if done.
069     *
070     * @return the next available reader or null
071     */
072    private Reader nextReader() {
073        return this.readers.hasNext() ? this.readers.next() : null;
074    }
075
076    /*
077     * (non-Javadoc)
078     *
079     * @see java.io.Reader#read(char[], int, int)
080     */
081    @Override
082    public int read() throws IOException {
083        int c = EOF;
084        while (reader != null) {
085            c = reader.read();
086            if (c != EOF) {
087                break;
088            }
089            reader = nextReader();
090        }
091        return c;
092    }
093
094    /*
095     * (non-Javadoc)
096     *
097     * @see java.io.Reader#read()
098     */
099    @Override
100    public int read(final char[] cbuf, int off, int len) throws IOException {
101        Objects.requireNonNull(cbuf, "cbuf");
102        if (len < 0 || off < 0 || off + len > cbuf.length) {
103            throw new IndexOutOfBoundsException("Array Size=" + cbuf.length + ", offset=" + off + ", length=" + len);
104        }
105        int count = 0;
106        while (reader != null) {
107            final int readLen = reader.read(cbuf, off, len);
108            if (readLen == EOF) {
109                reader = nextReader();
110            } else {
111                count += readLen;
112                off += readLen;
113                len -= readLen;
114                if (len <= 0) {
115                    break;
116                }
117            }
118        }
119        if (count > 0) {
120            return count;
121        }
122        return EOF;
123    }
124}