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.IOException;
21  import java.io.Reader;
22  import java.util.Iterator;
23  import java.util.NoSuchElementException;
24  
25  /**
26   * An Iterator over the lines in a <code>Reader</code>.
27   * <p>
28   * <code>LineIterator</code> holds a reference to an open <code>Reader</code>.
29   * When you have finished with the iterator you should close the reader
30   * to free internal resources. This can be done by closing the reader directly,
31   * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)}
32   * method on the iterator.
33   * <p>
34   * The recommended usage pattern is:
35   * <pre>
36   * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
37   * try {
38   *   while (it.hasNext()) {
39   *     String line = it.nextLine();
40   *     // do something with line
41   *   }
42   * } finally {
43   *   it.close();
44   * }
45   * </pre>
46   *
47   * @version $Id: LineIterator.java 1471767 2013-04-24 23:24:19Z sebb $
48   * @since 1.2
49   */
50  public class LineIterator implements Iterator<String> {
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      public boolean hasNext() {
88          if (cachedLine != null) {
89              return true;
90          } else if (finished) {
91              return false;
92          } else {
93              try {
94                  while (true) {
95                      final String line = bufferedReader.readLine();
96                      if (line == null) {
97                          finished = true;
98                          return false;
99                      } else if (isValidLine(line)) {
100                         cachedLine = line;
101                         return true;
102                     }
103                 }
104             } catch(final IOException ioe) {
105                 close();
106                 throw new IllegalStateException(ioe);
107             }
108         }
109     }
110 
111     /**
112      * Overridable method to validate each line that is returned.
113      * This implementation always returns true.
114      * @param line  the line that is to be validated
115      * @return true if valid, false to remove from the iterator
116      */
117     protected boolean isValidLine(final String line) {
118         return true;
119     }
120 
121     /**
122      * Returns the next line in the wrapped <code>Reader</code>.
123      *
124      * @return the next line from the input
125      * @throws NoSuchElementException if there is no line to return
126      */
127     public String next() {
128         return nextLine();
129     }
130 
131     /**
132      * Returns the next line in the wrapped <code>Reader</code>.
133      *
134      * @return the next line from the input
135      * @throws NoSuchElementException if there is no line to return
136      */
137     public String nextLine() {
138         if (!hasNext()) {
139             throw new NoSuchElementException("No more lines");
140         }
141         final String currentLine = cachedLine;
142         cachedLine = null;
143         return currentLine;
144     }
145 
146     /**
147      * Closes the underlying <code>Reader</code> quietly.
148      * This method is useful if you only want to process the first few
149      * lines of a larger file. If you do not close the iterator
150      * then the <code>Reader</code> remains open.
151      * This method can safely be called multiple times.
152      */
153     public void close() {
154         finished = true;
155         IOUtils.closeQuietly(bufferedReader);
156         cachedLine = null;
157     }
158 
159     /**
160      * Unsupported.
161      *
162      * @throws UnsupportedOperationException always
163      */
164     public void remove() {
165         throw new UnsupportedOperationException("Remove unsupported on LineIterator");
166     }
167 
168     //-----------------------------------------------------------------------
169     /**
170      * Closes the iterator, handling null and ignoring exceptions.
171      *
172      * @param iterator  the iterator to close
173      */
174     public static void closeQuietly(final LineIterator iterator) {
175         if (iterator != null) {
176             iterator.close();
177         }
178     }
179 
180 }