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