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