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 * @see Throwable#addSuppressed(Throwable)
60 * @deprecated As of 2.6 deprecated without replacement. Please use the try-with-resources statement or handle
61 * suppressed exceptions manually.
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 bufferedReader = IOUtils.buffer(Objects.requireNonNull(reader, "reader"));
86 }
87
88 /**
89 * Closes the underlying {@link Reader}.
90 * This method is useful if you only want to process the first few
91 * lines of a larger file. If you do not close the iterator
92 * then the {@link Reader} remains open.
93 * This method can safely be called multiple times.
94 *
95 * @throws IOException if closing the underlying {@link Reader} fails.
96 */
97 @Override
98 public void close() throws IOException {
99 finished = true;
100 cachedLine = null;
101 IOUtils.close(bufferedReader);
102 }
103
104 /**
105 * Indicates whether the {@link Reader} has more lines.
106 * If there is an {@link IOException} then {@link #close()} will
107 * be called on this instance.
108 *
109 * @return {@code true} if the Reader has more lines.
110 * @throws IllegalStateException if an IO exception occurs.
111 */
112 @Override
113 public boolean hasNext() {
114 if (cachedLine != null) {
115 return true;
116 }
117 if (finished) {
118 return false;
119 }
120 try {
121 while (true) {
122 final String line = bufferedReader.readLine();
123 if (line == null) {
124 finished = true;
125 return false;
126 }
127 if (isValidLine(line)) {
128 cachedLine = line;
129 return true;
130 }
131 }
132 } catch (final IOException ioe) {
133 throw new IllegalStateException(IOUtils.closeQuietlySuppress(this, ioe));
134 }
135 }
136
137 /**
138 * Overridable method to validate each line that is returned.
139 * This implementation always returns true.
140 *
141 * @param line the line that is to be validated.
142 * @return true if valid, false to remove from the iterator.
143 */
144 protected boolean isValidLine(final String line) {
145 return true;
146 }
147
148 /**
149 * Returns the next line in the wrapped {@link Reader}.
150 *
151 * @return the next line from the input.
152 * @throws NoSuchElementException if there is no line to return.
153 */
154 @Override
155 public String next() {
156 return nextLine();
157 }
158
159 /**
160 * Returns the next line in the wrapped {@link Reader}.
161 *
162 * @return the next line from the input.
163 * @throws NoSuchElementException if there is no line to return.
164 * @deprecated Use {@link #next()}.
165 */
166 @Deprecated
167 public String nextLine() {
168 if (!hasNext()) {
169 throw new NoSuchElementException("No more lines");
170 }
171 final String currentLine = cachedLine;
172 cachedLine = null;
173 return currentLine;
174 }
175
176 /**
177 * Unsupported.
178 *
179 * @throws UnsupportedOperationException always.
180 */
181 @Override
182 public void remove() {
183 throw new UnsupportedOperationException("remove not supported");
184 }
185
186 }