/* * 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.AbstractStreamTransformer_N_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_N_1} which receives specified type and streams * set of join's result to the destination . * * @param <K> type of keys * @param <L> type of data from left stream * @param <R> type of data from right stream * @param <V> type of output data */ public final class StreamJoin<K, L, R, V> extends AbstractStreamTransformer_N_1<V> { /** * It is primary interface of joiner. It contains methods which will join streams * * @param <K> type of keys * @param <L> type of data from left stream * @param <R> type of data from right stream * @param <V> type of output data */ public interface Joiner<K, L, R, V> { /** * 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<V> 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<V> output); } /** * Represent joiner which produce only inner joins * * @param <K> type of keys * @param <L> type of data from left stream * @param <R> type of data from right stream * @param <V> type of output data */ public static abstract class InnerJoiner<K, L, R, V> implements Joiner<K, L, R, V> { /** * 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<V> output) { } } /** * Simple implementation of Joiner, which does inner and left join * * @param <K> type of keys * @param <L> type of data from left stream * @param <R> type of data from right stream * @param <V> type of output data */ public static abstract class ValueJoiner<K, L, R, V> implements Joiner<K, L, R, V> { /** * 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<V> output) { V result = doInnerJoin(key, left, right); if (result != null) { output.onData(result); } } @Override public final void onLeftJoin(K key, L left, StreamDataReceiver<V> output) { V result = doLeftJoin(key, left); if (result != null) { output.onData(result); } } } private final Comparator<K> keyComparator; private final StreamConsumer<L> left; private final StreamConsumer<R> right; private final ArrayDeque<L> leftDeque = new ArrayDeque<>(); private final ArrayDeque<R> rightDeque = new ArrayDeque<>(); private final Function<L, K> leftKeyFunction; private final Function<R, K> rightKeyFunction; private final Joiner<K, L, R, V> joiner; // region creators private StreamJoin(Eventloop eventloop, Comparator<K> keyComparator, Function<L, K> leftKeyFunction, Function<R, K> rightKeyFunction, Joiner<K, L, R, V> joiner) { super(eventloop); this.keyComparator = checkNotNull(keyComparator); this.joiner = checkNotNull(joiner); this.left = addInput(new InputConsumer<>(leftDeque)); this.right = addInput(new InputConsumer<>(rightDeque)); this.leftKeyFunction = checkNotNull(leftKeyFunction); this.rightKeyFunction = checkNotNull(rightKeyFunction); this.outputProducer = new OutputProducer(); } /** * 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 static <K, L, R, V> StreamJoin<K, L, R, V> create(Eventloop eventloop, Comparator<K> keyComparator, Function<L, K> leftKeyFunction, Function<R, K> rightKeyFunction, Joiner<K, L, R, V> joiner) { return new StreamJoin<>(eventloop, keyComparator, leftKeyFunction, rightKeyFunction, joiner); } // endregion protected final class InputConsumer<I> extends AbstractInputConsumer<I> implements StreamDataReceiver<I> { private final ArrayDeque<I> deque; public InputConsumer(ArrayDeque<I> deque) { this.deque = deque; } @Override public StreamDataReceiver<I> getDataReceiver() { return this; } @Override public void onData(I item) { deque.add(item); outputProducer.produce(); } @Override protected void onUpstreamStarted() { outputProducer.produce(); } @Override protected void onUpstreamEndOfStream() { outputProducer.produce(); } } protected final class OutputProducer extends AbstractOutputProducer { @Override protected void onDownstreamStarted() { produce(); } @Override protected void onDownstreamSuspended() { suspendAllUpstreams(); } @Override protected void onDownstreamResumed() { resumeProduce(); } @Override protected void doProduce() { if (isStatusReady() && !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) { joiner.onLeftJoin(leftKey, leftValue, getDownstreamDataReceiver()); leftDeque.poll(); if (leftDeque.isEmpty()) break; leftValue = leftDeque.peek(); leftKey = leftKeyFunction.apply(leftValue); } else if (compare > 0) { rightDeque.poll(); if (rightDeque.isEmpty()) break; rightValue = rightDeque.peek(); rightKey = rightKeyFunction.apply(rightValue); } else { joiner.onInnerJoin(leftKey, leftValue, rightValue, getDownstreamDataReceiver()); leftDeque.poll(); if (leftDeque.isEmpty()) break; if (!isStatusReady()) break; leftValue = leftDeque.peek(); leftKey = leftKeyFunction.apply(leftValue); } } } if (isStatusReady()) { if (allUpstreamsEndOfStream()) { sendEndOfStream(); } else { resumeAllUpstreams(); } } } } /** * Returns left stream */ public StreamConsumer<L> getLeft() { return left; } /** * Returns right stream */ public StreamConsumer<R> getRight() { return right; } }