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                reader = nextReader();
088            } else {
089                break;
090            }
091        }
092        return c;
093    }
094
095    /*
096     * (non-Javadoc)
097     *
098     * @see java.io.Reader#read()
099     */
100    @Override
101    public int read(final char[] cbuf, int off, int len) throws IOException {
102        Objects.requireNonNull(cbuf, "cbuf");
103        if (len < 0 || off < 0 || off + len > cbuf.length) {
104            throw new IndexOutOfBoundsException("Array Size=" + cbuf.length + ", offset=" + off + ", length=" + len);
105        }
106        int count = 0;
107        while (reader != null) {
108            final int readLen = reader.read(cbuf, off, len);
109            if (readLen == EOF) {
110                reader = nextReader();
111            } else {
112                count += readLen;
113                off += readLen;
114                len -= readLen;
115                if (len <= 0) {
116                    break;
117                }
118            }
119        }
120        if (count > 0) {
121            return count;
122        }
123        return EOF;
124    }
125}