io.datakernel.stream.processor.StreamJoin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of async-streams Show documentation
Show all versions of async-streams Show documentation
Composable asynchronous/reactive streams with powerful data processing capabilities.
The newest version!
/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.datakernel.stream.processor;
import com.google.common.base.Function;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.stream.AbstractStreamConsumer;
import io.datakernel.stream.AbstractStreamTransformer_M_1;
import io.datakernel.stream.StreamConsumer;
import io.datakernel.stream.StreamDataReceiver;
import java.util.ArrayDeque;
import java.util.Comparator;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Represents a object which has left and right consumers, and one producer. After receiving data
* it can join it, available are inner join and left join. It work analogous joins from SQL.
* It is a {@link AbstractStreamTransformer_M_1} which receives specified type and streams
* set of join's result to the destination .
*
* @param type of keys
* @param type of data from left stream
* @param type of data from right stream
* @param type of output data
*/
public final class StreamJoin extends AbstractStreamTransformer_M_1 {
/**
* It is primary interface of joiner. It contains methods which will join streams
*
* @param type of keys
* @param type of data from left stream
* @param type of data from right stream
* @param type of output data
*/
public interface Joiner {
/**
* Streams objects with all fields from both received streams as long as there is a match
* between the keys in both items.
*
* @param key on this key it will join
* @param left left stream
* @param right right stream
* @param output callback for sending result
*/
void onInnerJoin(K key, L left, R right, StreamDataReceiver output);
/**
* Streams objects with all fields from the left stream , with the matching key - fields in the
* right stream. The field of result object is NULL in the right stream when there is no match.
*
* @param key on this key it will join
* @param left left stream
* @param output callback for sending result
*/
void onLeftJoin(K key, L left, StreamDataReceiver output);
}
/**
* Represent joiner which produce only inner joins
*
* @param type of keys
* @param type of data from left stream
* @param type of data from right stream
* @param type of output data
*/
public static abstract class InnerJoiner implements Joiner {
/**
* Left join does nothing for absence null fields in result inner join
*
* @param key on this key it will join
* @param left left stream
* @param output callback for sending result
*/
@Override
public void onLeftJoin(K key, L left, StreamDataReceiver output) {
}
}
/**
* Simple implementation of Joiner, which does inner and left join
*
* @param type of keys
* @param type of data from left stream
* @param type of data from right stream
* @param type of output data
*/
public static abstract class ValueJoiner implements Joiner {
/**
* Method which contains realization inner join.
*
* @param key on this key it will join
* @param left left stream
* @param right right stream
* @return stream with joined streams
*/
public abstract V doInnerJoin(K key, L left, R right);
/**
* Method which contains realization left join
*
* @param key on this key it will join
* @param left left stream
* @return stream with joined streams
*/
public V doLeftJoin(K key, L left) {
return null;
}
@Override
public final void onInnerJoin(K key, L left, R right, StreamDataReceiver output) {
V result = doInnerJoin(key, left, right);
if (result != null) {
output.onData(result);
}
}
@Override
public final void onLeftJoin(K key, L left, StreamDataReceiver output) {
V result = doLeftJoin(key, left);
if (result != null) {
output.onData(result);
}
}
}
private final Comparator keyComparator;
private final InternalConsumer left;
private final InternalConsumer right;
private final ArrayDeque leftDeque = new ArrayDeque<>();
private final ArrayDeque rightDeque = new ArrayDeque<>();
private final Function leftKeyFunction;
private final Function rightKeyFunction;
private final Joiner joiner;
/**
* Creates a new instance of StreamJoin
*
* @param eventloop eventloop in which runs reducer
* @param keyComparator comparator for compare keys
* @param leftKeyFunction function for counting keys of left stream
* @param rightKeyFunction function for counting keys of right stream
* @param joiner joiner which will join streams
*/
public StreamJoin(Eventloop eventloop, Comparator keyComparator,
Function leftKeyFunction, Function rightKeyFunction,
Joiner joiner) {
super(eventloop);
this.keyComparator = checkNotNull(keyComparator);
this.joiner = checkNotNull(joiner);
this.left = addInput(new InternalConsumer<>(eventloop, leftDeque));
this.right = addInput(new InternalConsumer<>(eventloop, rightDeque));
this.leftKeyFunction = checkNotNull(leftKeyFunction);
this.rightKeyFunction = checkNotNull(rightKeyFunction);
}
@Override
protected void doProduce() {
if (status == READY && !leftDeque.isEmpty() && !rightDeque.isEmpty()) {
L leftValue = leftDeque.peek();
K leftKey = leftKeyFunction.apply(leftValue);
R rightValue = rightDeque.peek();
K rightKey = rightKeyFunction.apply(rightValue);
for (; ; ) {
int compare = keyComparator.compare(leftKey, rightKey);
if (compare < 0) {
leftDeque.poll();
if (leftDeque.isEmpty())
break;
leftValue = leftDeque.peek();
leftKey = leftKeyFunction.apply(leftValue);
} else if (compare > 0) {
joiner.onLeftJoin(leftKey, leftValue, downstreamDataReceiver);
rightDeque.poll();
if (rightDeque.isEmpty())
break;
rightValue = rightDeque.peek();
rightKey = rightKeyFunction.apply(rightValue);
} else {
joiner.onInnerJoin(leftKey, leftValue, rightValue, downstreamDataReceiver);
leftDeque.poll();
if (leftDeque.isEmpty())
break;
if (status != READY)
break;
leftValue = leftDeque.peek();
leftKey = leftKeyFunction.apply(leftValue);
}
}
}
if (status == READY) {
if (left.getUpstreamStatus() == END_OF_STREAM && right.getUpstreamStatus() == END_OF_STREAM) {
sendEndOfStream();
} else {
resumeAllUpstreams();
}
}
}
@Override
protected void onSuspended() {
suspendAllUpstreams();
}
@Override
protected void onResumed() {
resumeProduce();
}
/**
* Returns left stream
*/
public StreamConsumer getLeft() {
return left;
}
/**
* Returns right stream
*/
public StreamConsumer getRight() {
return right;
}
private final class InternalConsumer extends AbstractStreamConsumer implements StreamDataReceiver {
private final ArrayDeque deque;
public InternalConsumer(Eventloop eventloop, ArrayDeque deque) {
super(eventloop);
this.deque = checkNotNull(deque);
}
@Override
public void onData(I item) {
deque.add(item);
produce();
}
@Override
public void onEndOfStream() {
produce();
}
@Override
public void onError(Exception e) {
upstreamProducer.closeWithError(e);
closeWithError(e);
}
@Override
public StreamDataReceiver getDataReceiver() {
return this;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy