    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     */
    017    package org.apache.commons.io;
    018
    019    import java.io.BufferedReader;
    020    import java.io.IOException;
    021    import java.io.Reader;
    022    import java.util.Iterator;
    023    import java.util.NoSuchElementException;
    024
    025    /**
    026     * An Iterator over the lines in a <code>Reader</code>.
    027     * <p>
    028     * <code>LineIterator</code> holds a reference to an open <code>Reader</code>.
    029     * When you have finished with the iterator you should close the reader
    030     * to free internal resources. This can be done by closing the reader directly,
    031     * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)}
    032     * method on the iterator.
    033     * <p>
    034     * The recommended usage pattern is:
    035     * <pre>
    036     * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
    037     * try {
    038     *   while (it.hasNext()) {
    039     *     String line = it.nextLine();
    040     *     // do something with line
    041     *   }
    042     * } finally {
    043     *   it.close();
    044     * }
    045     * </pre>
    046     *
    047     * @author Niall Pemberton
    048     * @author Stephen Colebourne
    049     * @author Sandy McArthur
    050     * @version $Id: LineIterator.java 1022322 2010-10-13 23:20:42Z ggregory $
    051     * @since Commons IO 1.2
    052     */
    053    public class LineIterator implements Iterator<String> {
    054
    055        /** The reader that is being read. */
    056        private final BufferedReader bufferedReader;
    057        /** The current line. */
    058        private String cachedLine;
    059        /** A flag indicating if the iterator has been fully read. */
    060        private boolean finished = false;
    061
    062        /**
    063         * Constructs an iterator of the lines for a <code>Reader</code>.
    064         *
    065         * @param reader the <code>Reader</code> to read from, not null
    066         * @throws IllegalArgumentException if the reader is null
    067         */
    068        public LineIterator(final Reader reader) throws IllegalArgumentException {
    069            if (reader == null) {
    070                throw new IllegalArgumentException("Reader must not be null");
    071            }
    072            if (reader instanceof BufferedReader) {
    073                bufferedReader = (BufferedReader) reader;
    074            } else {
    075                bufferedReader = new BufferedReader(reader);
    076            }
    077        }
    078
    079        //-----------------------------------------------------------------------
    080        /**
    081         * Indicates whether the <code>Reader</code> has more lines.
    082         * If there is an <code>IOException</code> then {@link #close()} will
    083         * be called on this instance.
    084         *
    085         * @return <code>true</code> if the Reader has more lines
    086         * @throws IllegalStateException if an IO exception occurs
    087         */
    088        public boolean hasNext() {
    089            if (cachedLine != null) {
    090                return true;
    091            } else if (finished) {
    092                return false;
    093            } else {
    094                try {
    095                    while (true) {
    096                        String line = bufferedReader.readLine();
    097                        if (line == null) {
    098                            finished = true;
    099                            return false;
    100                        } else if (isValidLine(line)) {
    101                            cachedLine = line;
    102                            return true;
    103                        }
    104                    }
    105                } catch(IOException ioe) {
    106                    close();
    107                    throw new IllegalStateException(ioe);
    108                }
    109            }
    110        }
    111
    112        /**
    113         * Overridable method to validate each line that is returned.
    114         *
    115         * @param line  the line that is to be validated
    116         * @return true if valid, false to remove from the iterator
    117         */
    118        protected boolean isValidLine(String line) {
    119            return true;
    120        }
    121
    122        /**
    123         * Returns the next line in the wrapped <code>Reader</code>.
    124         *
    125         * @return the next line from the input
    126         * @throws NoSuchElementException if there is no line to return
    127         */
    128        public String next() {
    129            return nextLine();
    130        }
    131
    132        /**
    133         * Returns the next line in the wrapped <code>Reader</code>.
    134         *
    135         * @return the next line from the input
    136         * @throws NoSuchElementException if there is no line to return
    137         */
    138        public String nextLine() {
    139            if (!hasNext()) {
    140                throw new NoSuchElementException("No more lines");
    141            }
    142            String currentLine = cachedLine;
    143            cachedLine = null;
    144            return currentLine;
    145        }
    146
    147        /**
    148         * Closes the underlying <code>Reader</code> quietly.
    149         * This method is useful if you only want to process the first few
    150         * lines of a larger file. If you do not close the iterator
    151         * then the <code>Reader</code> remains open.
    152         * This method can safely be called multiple times.
    153         */
    154        public void close() {
    155            finished = true;
    156            IOUtils.closeQuietly(bufferedReader);
    157            cachedLine = null;
    158        }
    159
    160        /**
    161         * Unsupported.
    162         *
    163         * @throws UnsupportedOperationException always
    164         */
    165        public void remove() {
    166            throw new UnsupportedOperationException("Remove unsupported on LineIterator");
    167        }
    168
    169        //-----------------------------------------------------------------------
    170        /**
    171         * Closes the iterator, handling null and ignoring exceptions.
    172         *
    173         * @param iterator  the iterator to close
    174         */
    175        public static void closeQuietly(LineIterator iterator) {
    176            if (iterator != null) {
    177                iterator.close();
    178            }
    179        }
    180
    181    }