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 */ 017 package org.apache.commons.io; 018 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.Reader; 022 import java.util.Iterator; 023 import java.util.NoSuchElementException; 024 025 /** 026 * An Iterator over the lines in a <code>Reader</code>. 027 * <p> 028 * <code>LineIterator</code> holds a reference to an open <code>Reader</code>. 029 * When you have finished with the iterator you should close the reader 030 * to free internal resources. This can be done by closing the reader directly, 031 * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)} 032 * method on the iterator. 033 * <p> 034 * The recommended usage pattern is: 035 * <pre> 036 * LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 037 * try { 038 * while (it.hasNext()) { 039 * String line = it.nextLine(); 040 * // do something with line 041 * } 042 * } finally { 043 * it.close(); 044 * } 045 * </pre> 046 * 047 * @author Stephen Colebourne 048 * @author Sandy McArthur 049 * @version $Id: LineIterator.java 1102128 2011-05-11 23:05:22Z sebb $ 050 * @since Commons IO 1.2 051 */ 052 public class LineIterator implements Iterator<String> { 053 054 // N.B. This class deliberately does not implement Iterable, see https://issues.apache.org/jira/browse/IO-181 055 056 /** The reader that is being read. */ 057 private final BufferedReader bufferedReader; 058 /** The current line. */ 059 private String cachedLine; 060 /** A flag indicating if the iterator has been fully read. */ 061 private boolean finished = false; 062 063 /** 064 * Constructs an iterator of the lines for a <code>Reader</code>. 065 * 066 * @param reader the <code>Reader</code> to read from, not null 067 * @throws IllegalArgumentException if the reader is null 068 */ 069 public LineIterator(final Reader reader) throws IllegalArgumentException { 070 if (reader == null) { 071 throw new IllegalArgumentException("Reader must not be null"); 072 } 073 if (reader instanceof BufferedReader) { 074 bufferedReader = (BufferedReader) reader; 075 } else { 076 bufferedReader = new BufferedReader(reader); 077 } 078 } 079 080 //----------------------------------------------------------------------- 081 /** 082 * Indicates whether the <code>Reader</code> has more lines. 083 * If there is an <code>IOException</code> then {@link #close()} will 084 * be called on this instance. 085 * 086 * @return <code>true</code> if the Reader has more lines 087 * @throws IllegalStateException if an IO exception occurs 088 */ 089 public boolean hasNext() { 090 if (cachedLine != null) { 091 return true; 092 } else if (finished) { 093 return false; 094 } else { 095 try { 096 while (true) { 097 String line = bufferedReader.readLine(); 098 if (line == null) { 099 finished = true; 100 return false; 101 } else if (isValidLine(line)) { 102 cachedLine = line; 103 return true; 104 } 105 } 106 } catch(IOException ioe) { 107 close(); 108 throw new IllegalStateException(ioe); 109 } 110 } 111 } 112 113 /** 114 * Overridable method to validate each line that is returned. 115 * 116 * @param line the line that is to be validated 117 * @return true if valid, false to remove from the iterator 118 */ 119 protected boolean isValidLine(String line) { 120 return true; 121 } 122 123 /** 124 * Returns the next line in the wrapped <code>Reader</code>. 125 * 126 * @return the next line from the input 127 * @throws NoSuchElementException if there is no line to return 128 */ 129 public String next() { 130 return nextLine(); 131 } 132 133 /** 134 * Returns the next line in the wrapped <code>Reader</code>. 135 * 136 * @return the next line from the input 137 * @throws NoSuchElementException if there is no line to return 138 */ 139 public String nextLine() { 140 if (!hasNext()) { 141 throw new NoSuchElementException("No more lines"); 142 } 143 String currentLine = cachedLine; 144 cachedLine = null; 145 return currentLine; 146 } 147 148 /** 149 * Closes the underlying <code>Reader</code> quietly. 150 * This method is useful if you only want to process the first few 151 * lines of a larger file. If you do not close the iterator 152 * then the <code>Reader</code> remains open. 153 * This method can safely be called multiple times. 154 */ 155 public void close() { 156 finished = true; 157 IOUtils.closeQuietly(bufferedReader); 158 cachedLine = null; 159 } 160 161 /** 162 * Unsupported. 163 * 164 * @throws UnsupportedOperationException always 165 */ 166 public void remove() { 167 throw new UnsupportedOperationException("Remove unsupported on LineIterator"); 168 } 169 170 //----------------------------------------------------------------------- 171 /** 172 * Closes the iterator, handling null and ignoring exceptions. 173 * 174 * @param iterator the iterator to close 175 */ 176 public static void closeQuietly(LineIterator iterator) { 177 if (iterator != null) { 178 iterator.close(); 179 } 180 } 181 182 }