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}