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 * https://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 */
17 package org.apache.commons.io;
18
19 import java.io.BufferedReader;
20 import java.io.Closeable;
21 import java.io.IOException;
22 import java.io.Reader;
23 import java.util.Iterator;
24 import java.util.NoSuchElementException;
25 import java.util.Objects;
26
27 /**
28 * An Iterator over the lines in a {@link Reader}.
29 * <p>
30 * {@link LineIterator} holds a reference to an open {@link Reader}.
31 * When you have finished with the iterator you should close the reader
32 * to free internal resources. This can be done by closing the reader directly,
33 * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)}
34 * method on the iterator.
35 * <p>
36 * The recommended usage pattern is:
37 * <pre>
38 * LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name());
39 * try {
40 * while (it.hasNext()) {
41 * String line = it.nextLine();
42 * // do something with line
43 * }
44 * } finally {
45 * it.close();
46 * }
47 * </pre>
48 *
49 * @since 1.2
50 */
51 public class LineIterator implements Iterator<String>, Closeable {
52
53 // This class deliberately does not implement Iterable, see https://issues.apache.org/jira/browse/IO-181
54
55 /**
56 * Closes a {@link LineIterator} quietly.
57 *
58 * @param iterator The iterator to close, or {@code null}.
59 * @deprecated As of 2.6 deprecated without replacement. Please use the try-with-resources statement or handle
60 * suppressed exceptions manually.
61 * @see Throwable#addSuppressed(Throwable)
62 */
63 @Deprecated
64 public static void closeQuietly(final LineIterator iterator) {
65 IOUtils.closeQuietly(iterator);
66 }
67
68 /** The reader that is being read. */
69 private final BufferedReader bufferedReader;
70
71 /** The current line. */
72 private String cachedLine;
73
74 /** A flag indicating if the iterator has been fully read. */
75 private boolean finished;
76
77 /**
78 * Constructs an iterator of the lines for a {@link Reader}.
79 *
80 * @param reader the {@link Reader} to read from, not null
81 * @throws NullPointerException if the reader is null
82 */
83 @SuppressWarnings("resource") // Caller closes Reader
84 public LineIterator(final Reader reader) {
85 Objects.requireNonNull(reader, "reader");
86 if (reader instanceof BufferedReader) {
87 bufferedReader = (BufferedReader) reader;
88 } else {
89 bufferedReader = new BufferedReader(reader);
90 }
91 }
92
93 /**
94 * Closes the underlying {@link Reader}.
95 * This method is useful if you only want to process the first few
96 * lines of a larger file. If you do not close the iterator
97 * then the {@link Reader} remains open.
98 * This method can safely be called multiple times.
99 *
100 * @throws IOException if closing the underlying {@link Reader} fails.
101 */
102 @Override
103 public void close() throws IOException {
104 finished = true;
105 cachedLine = null;
106 IOUtils.close(bufferedReader);
107 }
108
109 /**
110 * Indicates whether the {@link Reader} has more lines.
111 * If there is an {@link IOException} then {@link #close()} will
112 * be called on this instance.
113 *
114 * @return {@code true} if the Reader has more lines
115 * @throws IllegalStateException if an IO exception occurs
116 */
117 @Override
118 public boolean hasNext() {
119 if (cachedLine != null) {
120 return true;
121 }
122 if (finished) {
123 return false;
124 }
125 try {
126 while (true) {
127 final String line = bufferedReader.readLine();
128 if (line == null) {
129 finished = true;
130 return false;
131 }
132 if (isValidLine(line)) {
133 cachedLine = line;
134 return true;
135 }
136 }
137 } catch (final IOException ioe) {
138 IOUtils.closeQuietly(this, ioe::addSuppressed);
139 throw new IllegalStateException(ioe);
140 }
141 }
142
143 /**
144 * Overridable method to validate each line that is returned.
145 * This implementation always returns true.
146 * @param line the line that is to be validated
147 * @return true if valid, false to remove from the iterator
148 */
149 protected boolean isValidLine(final String line) {
150 return true;
151 }
152
153 /**
154 * Returns the next line in the wrapped {@link Reader}.
155 *
156 * @return the next line from the input
157 * @throws NoSuchElementException if there is no line to return
158 */
159 @Override
160 public String next() {
161 return nextLine();
162 }
163
164 /**
165 * Returns the next line in the wrapped {@link Reader}.
166 *
167 * @return the next line from the input
168 * @throws NoSuchElementException if there is no line to return
169 * @deprecated Use {@link #next()}.
170 */
171 @Deprecated
172 public String nextLine() {
173 if (!hasNext()) {
174 throw new NoSuchElementException("No more lines");
175 }
176 final String currentLine = cachedLine;
177 cachedLine = null;
178 return currentLine;
179 }
180
181 /**
182 * Unsupported.
183 *
184 * @throws UnsupportedOperationException always
185 */
186 @Override
187 public void remove() {
188 throw new UnsupportedOperationException("remove not supported");
189 }
190
191 }