All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.stream.processor.StreamJoin Maven / Gradle / Ivy

Go to download

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;
		}
	}

}