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.Closeable; 21 import java.io.IOException; 22 import java.io.Reader; 23 import java.util.Iterator; 24 import java.util.NoSuchElementException; 25 import java.util.Objects; 26 27 /** 28 * An Iterator over the lines in a {@link Reader}. 29 * <p> 30 * {@link LineIterator} holds a reference to an open {@link Reader}. 31 * When you have finished with the iterator you should close the reader 32 * to free internal resources. This can be done by closing the reader directly, 33 * or by calling the {@link #close()} or {@link #closeQuietly(LineIterator)} 34 * method on the iterator. 35 * <p> 36 * The recommended usage pattern is: 37 * <pre> 38 * LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name()); 39 * try { 40 * while (it.hasNext()) { 41 * String line = it.nextLine(); 42 * // do something with line 43 * } 44 * } finally { 45 * it.close(); 46 * } 47 * </pre> 48 * 49 * @since 1.2 50 */ 51 public class LineIterator implements Iterator<String>, Closeable { 52 53 // N.B. This class deliberately does not implement Iterable, see https://issues.apache.org/jira/browse/IO-181 54 55 /** 56 * Closes a {@link LineIterator} quietly. 57 * 58 * @param iterator The iterator to close, or {@code null}. 59 * @deprecated As of 2.6 deprecated without replacement. Please use the try-with-resources statement or handle 60 * suppressed exceptions manually. 61 * @see Throwable#addSuppressed(Throwable) 62 */ 63 @Deprecated closeQuietly(final LineIterator iterator)64 public static void closeQuietly(final LineIterator iterator) { 65 IOUtils.closeQuietly(iterator); 66 } 67 68 /** The reader that is being read. */ 69 private final BufferedReader bufferedReader; 70 71 /** The current line. */ 72 private String cachedLine; 73 74 /** A flag indicating if the iterator has been fully read. */ 75 private boolean finished; 76 77 /** 78 * Constructs an iterator of the lines for a {@link Reader}. 79 * 80 * @param reader the {@link Reader} to read from, not null 81 * @throws IllegalArgumentException if the reader is null 82 */ LineIterator(final Reader reader)83 public LineIterator(final Reader reader) throws IllegalArgumentException { 84 Objects.requireNonNull(reader, "reader"); 85 if (reader instanceof BufferedReader) { 86 bufferedReader = (BufferedReader) reader; 87 } else { 88 bufferedReader = new BufferedReader(reader); 89 } 90 } 91 92 /** 93 * Closes the underlying {@link Reader}. 94 * This method is useful if you only want to process the first few 95 * lines of a larger file. If you do not close the iterator 96 * then the {@link Reader} remains open. 97 * This method can safely be called multiple times. 98 * 99 * @throws IOException if closing the underlying {@link Reader} fails. 100 */ 101 @Override close()102 public void close() throws IOException { 103 finished = true; 104 cachedLine = null; 105 IOUtils.close(bufferedReader); 106 } 107 108 /** 109 * Indicates whether the {@link Reader} has more lines. 110 * If there is an {@link IOException} then {@link #close()} will 111 * be called on this instance. 112 * 113 * @return {@code true} if the Reader has more lines 114 * @throws IllegalStateException if an IO exception occurs 115 */ 116 @Override hasNext()117 public boolean hasNext() { 118 if (cachedLine != null) { 119 return true; 120 } 121 if (finished) { 122 return false; 123 } 124 try { 125 while (true) { 126 final String line = bufferedReader.readLine(); 127 if (line == null) { 128 finished = true; 129 return false; 130 } 131 if (isValidLine(line)) { 132 cachedLine = line; 133 return true; 134 } 135 } 136 } catch(final IOException ioe) { 137 IOUtils.closeQuietly(this, ioe::addSuppressed); 138 throw new IllegalStateException(ioe); 139 } 140 } 141 142 /** 143 * Overridable method to validate each line that is returned. 144 * This implementation always returns true. 145 * @param line the line that is to be validated 146 * @return true if valid, false to remove from the iterator 147 */ isValidLine(final String line)148 protected boolean isValidLine(final String line) { 149 return true; 150 } 151 152 /** 153 * Returns the next line in the wrapped {@link Reader}. 154 * 155 * @return the next line from the input 156 * @throws NoSuchElementException if there is no line to return 157 */ 158 @Override next()159 public String next() { 160 return nextLine(); 161 } 162 163 /** 164 * Returns the next line in the wrapped {@link Reader}. 165 * 166 * @return the next line from the input 167 * @throws NoSuchElementException if there is no line to return 168 */ nextLine()169 public String nextLine() { 170 if (!hasNext()) { 171 throw new NoSuchElementException("No more lines"); 172 } 173 final String currentLine = cachedLine; 174 cachedLine = null; 175 return currentLine; 176 } 177 178 /** 179 * Unsupported. 180 * 181 * @throws UnsupportedOperationException always 182 */ 183 @Override remove()184 public void remove() { 185 throw new UnsupportedOperationException("remove not supported"); 186 } 187 188 } 189