MultiReader.java
// Generated by delombok at Mon Nov 18 07:27:48 UTC 2024
package de.larssh.utils.io;
import java.io.IOException;
import java.io.Reader;
import java.util.Iterator;
import java.util.Optional;
import edu.umd.cs.findbugs.annotations.Nullable;
/**
* A {@code MultiReader} concatenates multiple {@link Reader}s into one. That
* might be useful for e.g. reading log files of multiple days.
*
* <p>
* The {@code readerIterator} needs to supply the {@link Reader}s to concatenate
* one after the other. {@link Reader}s, which have been read, are closed prior
* requesting the next.
*/
public class MultiReader extends Reader {
/**
* Value, that is returned by {@link Reader#read(char[], int, int)} on end of a
* {@link Reader}.
*/
private static final int END_OF_READER = -1;
/**
* If {@code true} this {@link MultiReader} reached its end, meaning
* {@link #readerIterator} has no next element.
*/
private boolean closed = false;
/**
* The current {@link Reader} or empty if the next needs to be supplied first or
* if this {@link MultiReader} is closed.
*/
private Optional<Reader> currentReader = Optional.empty();
/**
* {@link Iterator}, called whenever the next {@link Reader} needs to be
* supplied. If it has no next element, the {@link MultiReader} is closed.
*/
private final Iterator<Reader> readerIterator;
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
closed = true;
if (currentReader.isPresent()) {
currentReader.get().close();
currentReader = Optional.empty();
}
}
/**
* Determines the current {@link Reader} by updating the internal state of this
* {@link MultiReader} if necessary.
*
* @return the current {@link Reader} or empty if closed
*/
@SuppressWarnings({"checkstyle:SuppressWarnings", "resource"})
private Optional<Reader> getCurrentReader() {
if (closed) {
return Optional.empty();
}
if (currentReader.isPresent()) {
return currentReader;
}
currentReader = readerIterator.hasNext() ? Optional.of(readerIterator.next()) : Optional.empty();
if (!currentReader.isPresent()) {
closed = true;
}
return currentReader;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("checkstyle:SuppressWarnings")
public int read(@Nullable final char[] outputBuffer, final int offset, final int length) throws IOException {
final Optional<Reader> reader = getCurrentReader();
if (!reader.isPresent()) {
return END_OF_READER;
}
// Read from current reader
@SuppressWarnings("resource")
final int chars = reader.get().read(outputBuffer, offset, length);
if (chars == length) {
return chars;
}
// If less characters than expected have been read from the current reader,
// close it and continue with the next
currentReader = Optional.empty();
reader.get().close();
// Read additional chunk of characters
final int nonNegativeChars = chars == END_OF_READER ? 0 : chars;
final int nextChars = read(outputBuffer, offset + nonNegativeChars, length - nonNegativeChars);
return nonNegativeChars + (nextChars == END_OF_READER ? 0 : nextChars);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings({"checkstyle:SuppressWarnings", "resource"})
public boolean ready() throws IOException {
final Optional<Reader> reader = getCurrentReader();
return reader.isPresent() && reader.get().ready();
}
/**
* Creates a new {@code MultiReader} instance.
*
* @param readerIterator {@link Iterator}, called whenever the next {@link Reader} needs to be
* supplied. If it has no next element, the {@link MultiReader} is closed.
*/
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
public MultiReader(final Iterator<Reader> readerIterator) {
this.readerIterator = readerIterator;
}
}