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    }