package org.simpleflatmapper.util;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
public class ParallelReader extends Reader {
private static final int DEFAULT_MAX_READ = 8192;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 32;
private final Reader reader;
private final DataProducer dataProducer;
private final char[] buffer;
private final int bufferMask;
private final int maxRead;
private AtomicLong tail = new AtomicLong();
private AtomicLong head = new AtomicLong();
private final long capacity;
private long tailCache;
private long headCache;
private final long padding;
public ParallelReader(Reader reader, Executor executorService) {
this(reader, executorService, DEFAULT_BUFFER_SIZE);
}
public ParallelReader(Reader reader, Executor executorService, int bufferSize) {
this(reader, executorService, bufferSize, DEFAULT_MAX_READ);
}
public ParallelReader(Reader reader, Executor executorService, int bufferSize, int maxRead) {
int powerOf2 = 1 << 32 - Integer.numberOfLeadingZeros(bufferSize - 1);
padding = powerOf2 <= 1024 ? 0 : 64;
this.reader = reader;
buffer = new char[powerOf2];
bufferMask = buffer.length - 1;
dataProducer = new DataProducer();
executorService.execute(dataProducer);
capacity = buffer.length;
this.maxRead = maxRead;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
final long currentHead = head.get();
do {
if (currentHead < tailCache) {
int l = read(cbuf, off, len, currentHead, tailCache);
head.lazySet(currentHead + l);
return l;
}
tailCache = tail.get();
if (currentHead >= tailCache) {
if (!dataProducer.run) {
if (dataProducer.exception != null) {
throw dataProducer.exception;
}
tailCache = tail.get();
if (currentHead >= tailCache) {
return -1;
}
}
waitingStrategy();
}
} while(true);
}
@Override
public int read() throws IOException {
final long currentHead = head.get();
do {
if (currentHead < tailCache) {
int headIndex = (int) (currentHead & bufferMask);
char c = buffer[headIndex];
head.lazySet(currentHead + 1);
return c;
}
tailCache = tail.get();
if (currentHead >= tailCache) {
if (!dataProducer.run) {
if (dataProducer.exception != null) {
throw dataProducer.exception;
}
tailCache = tail.get();
if (currentHead >= tailCache) {
return -1;
}
}
waitingStrategy();
}
} while(true);
}
private void waitingStrategy() {
Thread.yield();
}
private int read(char[] cbuf, int off, int len, long currentHead, long currentTail) {
int headIndex = (int) (currentHead & bufferMask);
int usedLength = (int) (currentTail - currentHead);
int block1Length = Math.min(len, Math.min(usedLength, (int) (capacity - headIndex)));
int block2Length = Math.min(len, usedLength) - block1Length;
System.arraycopy(buffer, headIndex, cbuf, off, block1Length);
System.arraycopy(buffer, 0, cbuf, off+ block1Length, block2Length);
return block1Length + block2Length;
}
@Override
public void close() throws IOException {
dataProducer.stop();
reader.close();
}
private class DataProducer implements Runnable {
private volatile boolean run = true;
private volatile IOException exception;
@Override
public void run() {
long currentTail = tail.get();
while(run) {
final long wrapPoint = currentTail - buffer.length;
if (headCache - padding <= wrapPoint) {
headCache = head.get();
if (headCache <= wrapPoint) {
waitingStrategy();
continue;
}
}
try {
int r = read(currentTail, headCache);
if (r == -1) {
run = false;
} else {
currentTail += r;
tail.lazySet(currentTail);
}
} catch (IOException e) {
exception = e;
run = false;
}
}
}
private int read(long currentTail, long currentHead) throws IOException {
long used = currentTail - currentHead;
long length = Math.min(capacity - used, maxRead);
int tailIndex = (int) (currentTail & bufferMask);
int endBlock1 = (int) Math.min(tailIndex + length, capacity);
int block1Length = endBlock1 - tailIndex;
return reader.read(buffer, tailIndex, block1Length);
}
public void stop() {
run = false;
}
}
}