1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package org.apache.commons.io;
18
19import java.io.BufferedReader;
20import java.io.IOException;
21import java.io.Reader;
22import java.util.Iterator;
23import java.util.NoSuchElementException;
24
25/**
26 * An Iterator over the lines in a <code>Reader</code>.
27 * <p>
28 * <code>LineIterator</code> holds a reference to an open <code>Reader</code>.
29 * When you have finished with the iterator you should close the reader
30 * to free internal resources. This can be done by closing the reader directly,
31 * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)}
32 * method on the iterator.
33 * <p>
34 * The recommended usage pattern is:
35 * <pre>
36 * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
37 * try {
38 *   while (it.hasNext()) {
39 *     String line = it.nextLine();
40 *     /// do something with line
41 *   }
42 * } finally {
43 *   LineIterator.closeQuietly(iterator);
44 * }
45 * </pre>
46 *
47 * @author Niall Pemberton
48 * @author Stephen Colebourne
49 * @author Sandy McArthur
50 * @version $Id: LineIterator.java 437567 2006-08-28 06:39:07Z bayard $
51 * @since Commons IO 1.2
52 */
53public class LineIterator implements Iterator {
54
55    /** The reader that is being read. */
56    private final BufferedReader bufferedReader;
57    /** The current line. */
58    private String cachedLine;
59    /** A flag indicating if the iterator has been fully read. */
60    private boolean finished = false;
61
62    /**
63     * Constructs an iterator of the lines for a <code>Reader</code>.
64     *
65     * @param reader the <code>Reader</code> to read from, not null
66     * @throws IllegalArgumentException if the reader is null
67     */
68    public LineIterator(final Reader reader) throws IllegalArgumentException {
69        if (reader == null) {
70            throw new IllegalArgumentException("Reader must not be null");
71        }
72        if (reader instanceof BufferedReader) {
73            bufferedReader = (BufferedReader) reader;
74        } else {
75            bufferedReader = new BufferedReader(reader);
76        }
77    }
78
79    //-----------------------------------------------------------------------
80    /**
81     * Indicates whether the <code>Reader</code> has more lines.
82     * If there is an <code>IOException</code> then {@link #close()} will
83     * be called on this instance.
84     *
85     * @return <code>true</code> if the Reader has more lines
86     * @throws IllegalStateException if an IO exception occurs
87     */
88    public boolean hasNext() {
89        if (cachedLine != null) {
90            return true;
91        } else if (finished) {
92            return false;
93        } else {
94            try {
95                while (true) {
96                    String line = bufferedReader.readLine();
97                    if (line == null) {
98                        finished = true;
99                        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.toString());
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 Object 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}
182