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     * @version $Id: LineIterator.java 1307461 2012-03-30 15:12:29Z ggregory $
048     * @since 1.2
049     */
050    public class LineIterator implements Iterator<String> {
051    
052        // N.B. This class deliberately does not implement Iterable, see https://issues.apache.org/jira/browse/IO-181
053        
054        /** The reader that is being read. */
055        private final BufferedReader bufferedReader;
056        /** The current line. */
057        private String cachedLine;
058        /** A flag indicating if the iterator has been fully read. */
059        private boolean finished = false;
060    
061        /**
062         * Constructs an iterator of the lines for a <code>Reader</code>.
063         *
064         * @param reader the <code>Reader</code> to read from, not null
065         * @throws IllegalArgumentException if the reader is null
066         */
067        public LineIterator(final Reader reader) throws IllegalArgumentException {
068            if (reader == null) {
069                throw new IllegalArgumentException("Reader must not be null");
070            }
071            if (reader instanceof BufferedReader) {
072                bufferedReader = (BufferedReader) reader;
073            } else {
074                bufferedReader = new BufferedReader(reader);
075            }
076        }
077    
078        //-----------------------------------------------------------------------
079        /**
080         * Indicates whether the <code>Reader</code> has more lines.
081         * If there is an <code>IOException</code> then {@link #close()} will
082         * be called on this instance.
083         *
084         * @return {@code true} if the Reader has more lines
085         * @throws IllegalStateException if an IO exception occurs
086         */
087        public boolean hasNext() {
088            if (cachedLine != null) {
089                return true;
090            } else if (finished) {
091                return false;
092            } else {
093                try {
094                    while (true) {
095                        String line = bufferedReader.readLine();
096                        if (line == null) {
097                            finished = true;
098                            return false;
099                        } else if (isValidLine(line)) {
100                            cachedLine = line;
101                            return true;
102                        }
103                    }
104                } catch(IOException ioe) {
105                    close();
106                    throw new IllegalStateException(ioe);
107                }
108            }
109        }
110    
111        /**
112         * Overridable method to validate each line that is returned.
113         * This implementation always returns true.
114         * @param line  the line that is to be validated
115         * @return true if valid, false to remove from the iterator
116         */
117        protected boolean isValidLine(String line) {
118            return true;
119        }
120    
121        /**
122         * Returns the next line in the wrapped <code>Reader</code>.
123         *
124         * @return the next line from the input
125         * @throws NoSuchElementException if there is no line to return
126         */
127        public String next() {
128            return nextLine();
129        }
130    
131        /**
132         * Returns the next line in the wrapped <code>Reader</code>.
133         *
134         * @return the next line from the input
135         * @throws NoSuchElementException if there is no line to return
136         */
137        public String nextLine() {
138            if (!hasNext()) {
139                throw new NoSuchElementException("No more lines");
140            }
141            String currentLine = cachedLine;
142            cachedLine = null;
143            return currentLine;        
144        }
145    
146        /**
147         * Closes the underlying <code>Reader</code> quietly.
148         * This method is useful if you only want to process the first few
149         * lines of a larger file. If you do not close the iterator
150         * then the <code>Reader</code> remains open.
151         * This method can safely be called multiple times.
152         */
153        public void close() {
154            finished = true;
155            IOUtils.closeQuietly(bufferedReader);
156            cachedLine = null;
157        }
158    
159        /**
160         * Unsupported.
161         *
162         * @throws UnsupportedOperationException always
163         */
164        public void remove() {
165            throw new UnsupportedOperationException("Remove unsupported on LineIterator");
166        }
167    
168        //-----------------------------------------------------------------------
169        /**
170         * Closes the iterator, handling null and ignoring exceptions.
171         *
172         * @param iterator  the iterator to close
173         */
174        public static void closeQuietly(LineIterator iterator) {
175            if (iterator != null) {
176                iterator.close();
177            }
178        }
179    
180    }