TestReader.java
package internal.test;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.Objects;
/**
* This is a testing reader. It wraps a normal reader, then provides the {@link #setNextOperation(ReadOps)} to set
* behavior for next read operation.
*
* @author sunqian
*/
public class TestReader extends Reader {
private final @Nonnull Reader in;
private @Nonnull ReadOps readOps = ReadOps.READ_NORMAL;
private int times = 0;
private @Nullable Boolean markSupported = null;
/**
* Constructs with the specified wrapped reader.
*
* @param in the specified wrapped reader
*/
public TestReader(@Nonnull Reader in) {
this.in = in;
}
/**
* Sets the behavior for the next I/O operation. This method is equivalent to:
* <pre>{@code
* setNextOperations(readOps, 1);
* }</pre>
*
* @param readOps the behavior for the next I/O operation
* @see #setNextOperation(ReadOps, int)
*/
public void setNextOperation(@Nonnull ReadOps readOps) {
setNextOperation(readOps, 1);
}
/**
* Set the behaviors for the next specified number of I/O operations. After executing the specified number of times,
* the behaviors will be reset to normal.
*
* @param readOps the behaviors for the next specified number of I/O operations
* @param times the number of I/O operations
*/
public void setNextOperation(@Nonnull ReadOps readOps, int times) {
this.readOps = readOps;
this.times = times;
}
@Override
public int read() throws IOException {
switch (readOps) {
case READ_NORMAL:
return in.read();
case READ_ZERO:
case REACH_END: {
reduceTimes();
return -1;
}
default: {
reduceTimes();
throw new IOException();
}
}
}
@Override
public int read(char @Nonnull [] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(char @Nonnull [] b, int off, int len) throws IOException {
switch (readOps) {
case READ_NORMAL:
return in.read(b, off, len);
case READ_ZERO: {
reduceTimes();
return 0;
}
case REACH_END: {
reduceTimes();
return -1;
}
default: {
reduceTimes();
throw new IOException();
}
}
}
@Override
public long skip(long n) throws IOException {
switch (readOps) {
case READ_NORMAL:
return in.skip(n);
case READ_ZERO:
case REACH_END: {
reduceTimes();
return 0;
}
default: {
reduceTimes();
throw new IOException();
}
}
}
/**
* Sets the mark-supported for this {@link Reader}. If the {@code markSupported} is null, this {@link Reader} will
* directly use the mark-supported flag of wrapped source.
*
* @param markSupported the mark-supported flag, can be null
*/
public void markSupported(@Nullable Boolean markSupported) {
this.markSupported = markSupported;
}
@Override
public boolean markSupported() {
if (markSupported == null) {
return in.markSupported();
}
return markSupported;
}
@Override
public synchronized void mark(int readlimit) throws IOException {
if (Objects.equals(readOps, ReadOps.THROW)) {
reduceTimes();
throw new IOException();
} else {
in.mark(readlimit);
}
}
@Override
public synchronized void reset() throws IOException {
if (Objects.equals(readOps, ReadOps.THROW)) {
reduceTimes();
throw new IOException();
} else {
in.reset();
}
}
@Override
public void close() throws IOException {
if (Objects.equals(readOps, ReadOps.THROW)) {
reduceTimes();
throw new IOException();
} else {
in.close();
}
}
private void reduceTimes() {
times--;
if (times <= 0) {
times = 0;
readOps = ReadOps.READ_NORMAL;
}
}
}