View Javadoc
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 }