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