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    *      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   */
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      // N.B. 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      */
170     public String nextLine() {
171         if (!hasNext()) {
172             throw new NoSuchElementException("No more lines");
173         }
174         final String currentLine = cachedLine;
175         cachedLine = null;
176         return currentLine;
177     }
178 
179     /**
180      * Unsupported.
181      *
182      * @throws UnsupportedOperationException always
183      */
184     @Override
185     public void remove() {
186         throw new UnsupportedOperationException("remove not supported");
187     }
188 
189 }