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; 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 // N.B. 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 * @deprecated As of 2.6 deprecated without replacement. Please use the try-with-resources statement or handle 060 * suppressed exceptions manually. 061 * @see Throwable#addSuppressed(Throwable) 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 Objects.requireNonNull(reader, "reader"); 086 if (reader instanceof BufferedReader) { 087 bufferedReader = (BufferedReader) reader; 088 } else { 089 bufferedReader = new BufferedReader(reader); 090 } 091 } 092 093 /** 094 * Closes the underlying {@link Reader}. 095 * This method is useful if you only want to process the first few 096 * lines of a larger file. If you do not close the iterator 097 * then the {@link Reader} remains open. 098 * This method can safely be called multiple times. 099 * 100 * @throws IOException if closing the underlying {@link Reader} fails. 101 */ 102 @Override 103 public void close() throws IOException { 104 finished = true; 105 cachedLine = null; 106 IOUtils.close(bufferedReader); 107 } 108 109 /** 110 * Indicates whether the {@link Reader} has more lines. 111 * If there is an {@link IOException} then {@link #close()} will 112 * be called on this instance. 113 * 114 * @return {@code true} if the Reader has more lines 115 * @throws IllegalStateException if an IO exception occurs 116 */ 117 @Override 118 public boolean hasNext() { 119 if (cachedLine != null) { 120 return true; 121 } 122 if (finished) { 123 return false; 124 } 125 try { 126 while (true) { 127 final String line = bufferedReader.readLine(); 128 if (line == null) { 129 finished = true; 130 return false; 131 } 132 if (isValidLine(line)) { 133 cachedLine = line; 134 return true; 135 } 136 } 137 } catch (final IOException ioe) { 138 IOUtils.closeQuietly(this, ioe::addSuppressed); 139 throw new IllegalStateException(ioe); 140 } 141 } 142 143 /** 144 * Overridable method to validate each line that is returned. 145 * This implementation always returns true. 146 * @param line the line that is to be validated 147 * @return true if valid, false to remove from the iterator 148 */ 149 protected boolean isValidLine(final String line) { 150 return true; 151 } 152 153 /** 154 * Returns the next line in the wrapped {@link Reader}. 155 * 156 * @return the next line from the input 157 * @throws NoSuchElementException if there is no line to return 158 */ 159 @Override 160 public String next() { 161 return nextLine(); 162 } 163 164 /** 165 * Returns the next line in the wrapped {@link Reader}. 166 * 167 * @return the next line from the input 168 * @throws NoSuchElementException if there is no line to return 169 * @deprecated Use {@link #next()}. 170 */ 171 @Deprecated 172 public String nextLine() { 173 if (!hasNext()) { 174 throw new NoSuchElementException("No more lines"); 175 } 176 final String currentLine = cachedLine; 177 cachedLine = null; 178 return currentLine; 179 } 180 181 /** 182 * Unsupported. 183 * 184 * @throws UnsupportedOperationException always 185 */ 186 @Override 187 public void remove() { 188 throw new UnsupportedOperationException("remove not supported"); 189 } 190 191}