com.kolibrifx.plovercrest.server.internal.folds.FoldReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
The newest version!
/*
* Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
*/
package com.kolibrifx.plovercrest.server.internal.folds;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.tuple.ImmutablePair;
import com.kolibrifx.plovercrest.server.streams.PollableReader;
import com.kolibrifx.plovercrest.server.streams.ReaderPosition;
import com.kolibrifx.plovercrest.server.streams.Stream;
import com.kolibrifx.plovercrest.server.streams.StreamDataTriggerer;
import com.kolibrifx.plovercrest.server.streams.StreamObserver;
import com.kolibrifx.plovercrest.server.streams.folds.CombinatorStrategy;
/**
* A generic {@link PollableReader} implementation for folds.
*
* @param
* the output stream type
* @param
* the common input stream type (or {@link Object} if there is no common type)
*/
public class FoldReader implements PollableReader {
private final List> peekableStreams = new ArrayList<>();
private final CombinatorStrategy combinatorStrategy;
private final FoldCallback foldCallback;
private final StreamObserver outputObserver;
private int outputIndex = 0;
private long lastTimestamp;
private int lastTimestampCount;
private final Queue> outputQueue;
public FoldReader(final long readerTimestamp,
final CombinatorStrategy combinatorStrategy,
final StreamObserver outputObserver,
final FoldReaderInputs inputSpec,
final FoldCallback foldCallback,
final Queue> outputQueue) {
this.lastTimestamp = readerTimestamp;
this.lastTimestampCount = 0;
this.combinatorStrategy = combinatorStrategy;
this.outputObserver = outputObserver;
this.foldCallback = foldCallback;
this.outputQueue = outputQueue;
for (final ImmutablePair, Long> input : inputSpec.getInputs()) {
peekableStreams.add(PeekableInputStreamUtils.create(input.left, input.right));
}
}
@SuppressWarnings("unchecked")
@Override
public boolean poll() {
while (outputQueue.isEmpty()) {
if (!combinatorStrategy.tryConsumeNext(peekableStreams, foldCallback)) {
// Reaching the end can trigger output for some folds (due to take-previous behavior),
// so do not assume that the output queue is empty here.
break;
}
}
if (outputQueue.isEmpty()) {
outputObserver.onObserveEnd(lastTimestamp, outputIndex);
return false;
}
assert !outputQueue.isEmpty();
Timestamped latestOutput = outputQueue.poll();
assert latestOutput != null;
final long nextTimestamp = latestOutput.getTimestamp();
if (nextTimestamp == lastTimestamp) {
lastTimestampCount++;
} else {
lastTimestamp = nextTimestamp;
lastTimestampCount = 1;
}
T value = latestOutput.getValue();
// For binary data, peekable readers store byte arrays, but observers want ByteBuffer.
// FIXME: this hack suggests that the generics setup for PeekableInputStream is flawed,
// should find a better way to do this conversion.
if (value instanceof byte[]) {
value = (T) ByteBuffer.wrap((byte[]) value);
}
outputObserver.onObserve(lastTimestamp, outputIndex++, value);
latestOutput = null;
return true;
}
@Override
public void registerDataTriggerer(final StreamDataTriggerer triggerer) {
final StreamDataTriggerer childTriggerer = new StreamDataTriggerer() {
AtomicBoolean done = new AtomicBoolean(false);
@Override
public void wakeUpNow() {
if (!done.compareAndSet(false, true)) {
return;
}
triggerer.wakeUpNow();
}
@Override
public void wakeUpAtTime(final long clockMillis) {
// there's no easy "done" logic for wakeUpAtTime, so this could be triggered multiple times
triggerer.wakeUpAtTime(clockMillis);
}
};
int activeInputTriggerers = 0;
for (final PeekableInputStream> input : peekableStreams) {
if (input.registerDataTriggererIfReachedEnd(childTriggerer)) {
activeInputTriggerers++;
}
}
if (activeInputTriggerers == 0) {
// no inputs were at the end: this implies that data is available right now
childTriggerer.wakeUpNow();
}
}
@Override
public ReaderPosition getReaderPosition() {
return ReaderPosition.createTimestampPosition(lastTimestamp, lastTimestampCount);
}
}