org.jooq.lambda.SeqBuffer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jool Show documentation
Show all versions of jool Show documentation
jOOλ is part of the jOOQ series (along with jOOQ, jOOX, jOOR, jOOU) providing some useful extensions to Java 8 lambdas.
package org.jooq.lambda;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* Lazily consumes given Spliterator
through Seq
s provided by method seq()
.
* This method may be called multiple times, and the returned Seq
s may be consumed interchangeably.
*
* Instances of this class ARE thread-safe.
*
* @author Tomasz Linkowski
*/
final class SeqBuffer {
@SuppressWarnings("unchecked")
static SeqBuffer of(Stream stream) {
return of((Spliterator) stream.spliterator());
}
static SeqBuffer of(Spliterator spliterator) {
if (spliterator instanceof SeqBuffer.BufferSpliterator) {
return ((SeqBuffer.BufferSpliterator) spliterator).parentSeqBuffer(); // reuse existing SeqBuffer
}
return new SeqBuffer<>(spliterator);
}
private final Spliterator source;
private final List buffer = new ArrayList<>();
/**
* True
while source
hasn't reported that it's exhausted.
*
* This volatile field acts as a memory barrier for the contents of source
and buffer
:
*
* @link http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile
*/
private volatile boolean buffering = true;
private SeqBuffer(Spliterator source) {
this.source = Objects.requireNonNull(source);
}
/**
* Returns a Seq
over given source
.
*
* Although such Seq
is not thread-safe by itself, it interacts in a thread-safe way with its
* parent SeqBuffer
. As a result, each Seq
returned by this method can be safely
* used on a different thread.
*/
Seq seq() {
return Seq.seq(new BufferSpliterator());
}
/**
* Special Spliterator
whose tryAdvance
method can buffer
* (i.e. can advance the source
spliterator).
*
* Instances of this class are NOT thread-safe but they interact in a thread-safe way with SeqBuffer
.
*/
private class BufferSpliterator implements Spliterator {
/**
* Index of the element that will be returned upon next call to tryAdvance
if such element exists.
*/
private int nextIndex = 0;
//
// TRY ADVANCE
//
@Override
public boolean tryAdvance(Consumer action) {
return buffering // volatile-read (ensures buffer is up-to-date)
? tryAdvanceThisWithBuffering(action) // slow (synchronized)
: tryAdvanceThisAtOnce(action); // fast (not synchronized)
}
/**
* Tries to advance this Spliterator
to element at nextIndex
,
* buffering source
elements into buffer
if necessary.
*
* Synchronized on buffer
in order to:
* - obtain accurate buffer.size()
* - safely copy from source
to buffer
(if needed)
* - safely call buffer.get()
*/
private boolean tryAdvanceThisWithBuffering(Consumer action) {
final T next;
synchronized (buffer) {
if (!canAdvanceThisWithBuffering())
return false;
next = advanceThis();
}
action.accept(next); // call "action" outside of synchronized block
return true;
}
private boolean canAdvanceThisWithBuffering() {
return canAdvanceThisAtOnce() || tryAdvanceSource() && canAdvanceThisAtOnce();
}
private boolean canAdvanceThisAtOnce() {
return nextIndex < buffer.size();
}
/**
* Buffers (i.e. advances the source
) until an item at nextIndex
* is added to the buffer
, or until the source
is exhausted.
*
* Guarded by: buffer
*/
private boolean tryAdvanceSource() {
boolean canAdvanceSource = buffering; // volatile-read (stored in local variable to limit R/W operations)
if (!canAdvanceSource) // check again after having synchronized
return false;
do {
canAdvanceSource = source.tryAdvance(buffer::add);
} while (canAdvanceSource && !canAdvanceThisAtOnce());
// volatile-write (causes grown buffer and shrunk source to be visible to all threads upon next volatile-read)
buffering = canAdvanceSource;
return true;
}
private T advanceThis() {
return buffer.get(nextIndex++);
}
/**
* Called only when buffering has been completed.
*/
private boolean tryAdvanceThisAtOnce(Consumer action) {
if (!canAdvanceThisAtOnce())
return false;
action.accept(advanceThis());
return true;
}
//
// ESTIMATE SIZE
//
@Override
public long estimateSize() {
return buffering // volatile-read (ensures buffer is up-to-date)
? estimateSizeDuringBuffering() // slow (synchronized)
: numberOfElementsLeftInBuffer(); // fast (not synchronized)
}
/**
* Returns the estimate size of this Spliterator.
*
* Synchronized to get an accurate sum of buffer.size()
and source.estimateSize()
.
*/
private long estimateSizeDuringBuffering() {
synchronized (buffer) {
int leftInBuffer = numberOfElementsLeftInBuffer();
if (!buffering) // check again after having synchronized
return leftInBuffer;
long estimateSize = leftInBuffer + source.estimateSize();
// will overflow to negative number if source.estimateSize() reports Long.MAX_VALUE
return estimateSize >= 0 ? estimateSize : Long.MAX_VALUE;
}
}
private int numberOfElementsLeftInBuffer() {
return buffer.size() - nextIndex;
}
//
// REMAINING
//
@Override
public Spliterator trySplit() {
return null;
}
@Override
public int characteristics() {
// no synchronization here because source.characteristics() is assumed to be thread-safe
return (source.characteristics() & ~Spliterator.CONCURRENT) | Spliterator.ORDERED
| (buffering ? 0 : Spliterator.SIZED);
}
@Override
public Comparator getComparator() {
return source.getComparator();
}
SeqBuffer parentSeqBuffer() {
return SeqBuffer.this;
}
}
}