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 }