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