com.kolibrifx.plovercrest.server.internal.folds.AbstractPeekableInputStream 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 com.kolibrifx.plovercrest.server.internal.streams.NullStreamObserver;
import com.kolibrifx.plovercrest.server.streams.PollableReader;
import com.kolibrifx.plovercrest.server.streams.Stream;
import com.kolibrifx.plovercrest.server.streams.StreamDataTriggerer;
import com.kolibrifx.plovercrest.server.streams.StreamObserver;
/**
* Base implementation of a peekable stream. Subclasses need to specify how to convert from the
* input to output type.
*
* @param
* The output type.
* @param
* The input stream type.
*/
public abstract class AbstractPeekableInputStream implements PeekableInputStream {
private PollableReader reader = null;
private long peekTimestamp = Long.MIN_VALUE;
private boolean hasPeekData = false;
private T peekElement = null;
private boolean reachedEnd = false;
private final long fromTimestamp;
private final Stream stream;
private final StreamObserver streamObserver = new StreamObserver() {
@Override
public void onObserve(final long timestamp, final long index, final U element) {
hasPeekData = true;
peekTimestamp = timestamp;
peekElement = convertElement(element);
reachedEnd = false;
}
@Override
public void onObserveEnd(final long lastValidTimestamp, final long elementCount) {
reachedEnd = true;
}
@Override
public void onObserveFrozen() {
}
};
public AbstractPeekableInputStream(final Stream stream, final long fromTimestamp) {
this.stream = stream;
this.fromTimestamp = fromTimestamp;
}
protected abstract T convertElement(U element);
private void tryPeekNext() {
if (hasPeekData) {
return;
}
if (reader == null) {
reader = stream.createReaderFromTimestamp(fromTimestamp, streamObserver);
if (stream.getLastTimestamp() < fromTimestamp) {
// The stream does not (yet) have enough data to start. We abort, but also need to make sure the
// data triggerer wakes us up when more data is available. This is done by reading to the end,
// then registering a data triggerer, unless we suddenly have enough data available anyway.
// This logic is a bit tricky, but should work.
boolean shouldAbort = true;
while (reader.poll()) {
if (stream.getLastTimestamp() >= fromTimestamp) {
// Enough data became available while polling, so we don't have to abort anyway.
// Just create a new reader, and we're done.
reader = stream.createReaderFromTimestamp(fromTimestamp, streamObserver);
shouldAbort = false;
break;
}
}
if (shouldAbort) {
hasPeekData = false;
reader = null;
return;
}
}
}
final boolean r = reader.poll();
assert r == !reachedEnd;
}
@Override
public boolean hasNext() {
tryPeekNext();
return hasPeekData;
}
@Override
public long nextTimestamp() {
if (hasPeekData) {
return peekTimestamp;
} else {
throw new IllegalStateException("No next timestamp, only call if hasNext() is true");
}
}
@Override
public void consumeNext(final FoldCallback super T> cb) {
if (hasPeekData) {
cb.onNext(stream.getName(), peekTimestamp, peekElement);
hasPeekData = false;
} else {
throw new IllegalStateException("Nothing to consume, only call if hasNext() is true");
}
}
@Override
public boolean registerDataTriggererIfReachedEnd(final StreamDataTriggerer dataTriggerer) {
if (hasPeekData || !reachedEnd) {
return false;
}
if (reader == null) {
// This happens if the stream did not have enough data to start when tryPeekNext() was last called.
if (stream.getLastTimestamp() >= fromTimestamp) {
// Enough data is available now
dataTriggerer.wakeUpNow();
return true;
}
// Create a temporary reader, poll until the end is reached, then register the data triggerer.
final PollableReader tmpReader =
stream.createReaderFromTimestamp(fromTimestamp, new NullStreamObserver());
while (tmpReader.poll()) {
if (stream.getLastTimestamp() >= fromTimestamp) {
// Enough data became available while polling.
dataTriggerer.wakeUpNow();
return true;
}
}
tmpReader.registerDataTriggerer(dataTriggerer);
} else {
reader.registerDataTriggerer(dataTriggerer);
}
return true;
}
}