001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io; 018 019import java.io.BufferedReader; 020import java.io.Closeable; 021import java.io.IOException; 022import java.io.Reader; 023import java.util.Iterator; 024import java.util.NoSuchElementException; 025 026/** 027 * An Iterator over the lines in a <code>Reader</code>. 028 * <p> 029 * <code>LineIterator</code> holds a reference to an open <code>Reader</code>. 030 * When you have finished with the iterator you should close the reader 031 * to free internal resources. This can be done by closing the reader directly, 032 * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)} 033 * method on the iterator. 034 * <p> 035 * The recommended usage pattern is: 036 * <pre> 037 * LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 038 * try { 039 * while (it.hasNext()) { 040 * String line = it.nextLine(); 041 * // do something with line 042 * } 043 * } finally { 044 * it.close(); 045 * } 046 * </pre> 047 * 048 * @since 1.2 049 */ 050public class LineIterator implements Iterator<String>, Closeable { 051 052 // N.B. This class deliberately does not implement Iterable, see https://issues.apache.org/jira/browse/IO-181 053 054 /** The reader that is being read. */ 055 private final BufferedReader bufferedReader; 056 /** The current line. */ 057 private String cachedLine; 058 /** A flag indicating if the iterator has been fully read. */ 059 private boolean finished = false; 060 061 /** 062 * Constructs an iterator of the lines for a <code>Reader</code>. 063 * 064 * @param reader the <code>Reader</code> to read from, not null 065 * @throws IllegalArgumentException if the reader is null 066 */ 067 public LineIterator(final Reader reader) throws IllegalArgumentException { 068 if (reader == null) { 069 throw new IllegalArgumentException("Reader must not be null"); 070 } 071 if (reader instanceof BufferedReader) { 072 bufferedReader = (BufferedReader) reader; 073 } else { 074 bufferedReader = new BufferedReader(reader); 075 } 076 } 077 078 //----------------------------------------------------------------------- 079 /** 080 * Indicates whether the <code>Reader</code> has more lines. 081 * If there is an <code>IOException</code> then {@link #close()} will 082 * be called on this instance. 083 * 084 * @return {@code true} if the Reader has more lines 085 * @throws IllegalStateException if an IO exception occurs 086 */ 087 @Override 088 public boolean hasNext() { 089 if (cachedLine != null) { 090 return true; 091 } else if (finished) { 092 return false; 093 } else { 094 try { 095 while (true) { 096 final String line = bufferedReader.readLine(); 097 if (line == null) { 098 finished = true; 099 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}