/*
* 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 io.datakernel.stream.StreamDataReceiver;
/**
* Static utility methods pertaining to {@link StreamReducers.Reducer}.
* Contains primary ready for use reducers.
*/
public final class StreamReducers {
private StreamReducers() {
}
/**
* Returns reducer which streams only one element from group of same keys.
*
* @param <K> type of key
* @param <T> type of output
*/
public static <K, T> Reducer<K, T, T, Void> mergeDeduplicateReducer() {
return new MergeDeduplicateReducer<>();
}
/**
* Returns reducer which streams all receives elements sorted by keys.
*
* @param <K> type of key
* @param <T> type of output
*/
public static <K, T> Reducer<K, T, T, Void> mergeSortReducer() {
return new MergeSortReducer<>();
}
/**
* It is primary interface of Reducer.
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public interface Reducer<K, I, O, A> {
/**
* Run when reducer received first element with key from argument.
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
* @return accumulator for further operations
*/
A onFirstItem(StreamDataReceiver<O> stream, K key, I firstValue);
/**
* Run when reducer received each next element with key from argument
*
* @param stream stream where to send result
* @param key key of element
* @param nextValue received value
* @param accumulator accumulator which contains results of all previous operations
* @return accumulator for further operations
*/
A onNextItem(StreamDataReceiver<O> stream, K key, I nextValue, A accumulator);
/**
* Run after receiving last element with key from argument
*
* @param stream stream where to send result
* @param key key of element
* @param accumulator accumulator which contains results of all previous operations
*/
void onComplete(StreamDataReceiver<O> stream, K key, A accumulator);
}
/**
* Represents a helpful class which contains methods for simple casting types of input and
* output streams
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public abstract static class ReducerToResult<K, I, O, A> {
/**
* Creates a new accumulator with key from argument
*
* @param key key for new accumulator
* @return new accumulator
*/
public abstract A createAccumulator(K key);
/**
* Processing value with accumulator
*
* @param accumulator accumulator with partials results
* @param value received value for accumulating
* @return changing accumulator
*/
public abstract A accumulate(A accumulator, I value);
/**
* Combines two accumulators from argument.
*
* @return new accumulator
*/
public A combine(A accumulator, A anotherAccumulator) {
throw new UnsupportedOperationException("can not combine two accumulators");
}
/**
* Calls after completing receiving results for some key. It processed
* obtained accumulator and returns stream of output type from generic
*
* @param accumulator obtained accumulator after end receiving
* @return stream of output type from generic
*/
public abstract O produceResult(A accumulator);
/**
* Creates a new reducer which receives items,accumulated it, produces after end of stream
* and streams result
*/
public final Reducer<K, I, O, A> inputToOutput() {
return new InputToOutput<>(this);
}
/**
* Creates a new reducer which receives items,accumulated it and streams obtained accumulator
*/
public final Reducer<K, I, A, A> inputToAccumulator() {
return new InputToAccumulator<>(this);
}
/**
* Creates a new reducer which receives accumulators,combines it, produces after end of stream
* and streams result
*/
public final Reducer<K, A, O, A> accumulatorToOutput() {
return new AccumulatorToOutput<>(this);
}
/**
* Creates a new reducer which receives accumulators,combines it, and streams obtained accumulator
*/
public final Reducer<K, A, A, A> accumulatorToAccumulator() {
return new AccumulatorToAccumulator<>(this);
}
/**
* Represents a reducer which contains ReducerToResult where identified methods for processing
* items . After searching accumulator performs some action with it with method produceResult
* from ReducerToResult.
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public static final class InputToOutput<K, I, O, A> implements Reducer<K, I, O, A> {
private ReducerToResult<K, I, O, A> reducerToResult;
/**
* Creates a new instance of InputToOutput with ReducerToResult from arguments
*/
public InputToOutput(ReducerToResult<K, I, O, A> reducerToResult) {
this.reducerToResult = reducerToResult;
}
/**
* Creates accumulator with ReducerToResult and accumulates with it first item
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
* @return accumulator with result
*/
@Override
public final A onFirstItem(StreamDataReceiver<O> stream, K key, I firstValue) {
A accumulator = reducerToResult.createAccumulator(key);
return reducerToResult.accumulate(accumulator, firstValue);
}
/**
* Accumulates each next element.
*
* @param stream stream where to send result
* @param key key of element
* @param nextValue received value
* @param accumulator accumulator which contains results of all previous operations
* @return accumulator with result
*/
@Override
public final A onNextItem(StreamDataReceiver<O> stream, K key, I nextValue, A accumulator) {
return reducerToResult.accumulate(accumulator, nextValue);
}
/**
* Produces result accumulator with ReducerToResult and streams it
*
* @param stream stream where to send result
* @param key key of element
* @param accumulator accumulator which contains results of all previous operations
*/
@Override
public final void onComplete(StreamDataReceiver<O> stream, K key, A accumulator) {
stream.onData(reducerToResult.produceResult(accumulator));
}
}
/**
* Represents a reducer which contains ReducerToResult where identified methods for processing
* items . Streams obtained accumulator on complete.
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public static final class InputToAccumulator<K, I, O, A> implements Reducer<K, I, A, A> {
private ReducerToResult<K, I, O, A> reducerToResult;
/**
* Creates a new instance of InputToAccumulator with ReducerToResult from argument
*/
public InputToAccumulator(ReducerToResult<K, I, O, A> reducerToResult) {
this.reducerToResult = reducerToResult;
}
/**
* Creates accumulator with ReducerToResult and accumulates with it first item
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
* @return accumulator with result
*/
@Override
public A onFirstItem(StreamDataReceiver<A> stream, K key, I firstValue) {
A accumulator = reducerToResult.createAccumulator(key);
return reducerToResult.accumulate(accumulator, firstValue);
}
/**
* Accumulates each next element.
*
* @param stream stream where to send result
* @param key key of element
* @param nextValue received value
* @param accumulator accumulator which contains results of all previous operations
* @return accumulator with result
*/
@Override
public A onNextItem(StreamDataReceiver<A> stream, K key, I nextValue, A accumulator) {
return reducerToResult.accumulate(accumulator, nextValue);
}
/**
* Streams obtained accumulator
*
* @param stream stream where to send result
* @param key key of element
* @param accumulator accumulator which contains results of all previous operations
*/
@Override
public void onComplete(StreamDataReceiver<A> stream, K key, A accumulator) {
stream.onData(accumulator);
}
}
/**
* Represents a reducer which contains ReducerToResult where identified methods for processing
* items . Each received item is accumulator and it combines with previous value. After
* searching accumulator performs some action with it with method produceResult from
* ReducerToResult.
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public static final class AccumulatorToOutput<K, I, O, A> implements Reducer<K, A, O, A> {
private ReducerToResult<K, I, O, A> reducerToResult;
/**
* Creates a new instance of InputToAccumulator with ReducerToResult from argument
*/
public AccumulatorToOutput(ReducerToResult<K, I, O, A> reducerToResult) {
this.reducerToResult = reducerToResult;
}
/**
* Creates accumulator which is first item
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
* @return accumulator with result
*/
@Override
public A onFirstItem(StreamDataReceiver<O> stream, K key, A firstValue) {
return firstValue;
}
/**
* Combines previous accumulator and each next item
*
* @param stream stream where to send result
* @param key key of element
* @param nextValue received value
* @param accumulator accumulator which contains results of all previous combining
* @return accumulator with result
*/
@Override
public A onNextItem(StreamDataReceiver<O> stream, K key, A nextValue, A accumulator) {
return reducerToResult.combine(accumulator, nextValue);
}
/**
* Produces result accumulator with ReducerToResult and streams it
*
* @param stream stream where to send result
* @param key key of element
* @param accumulator accumulator which contains results of all previous operations
*/
@Override
public void onComplete(StreamDataReceiver<O> stream, K key, A accumulator) {
stream.onData(reducerToResult.produceResult(accumulator));
}
}
/**
* Represents a reducer which contains ReducerToResult where identified methods for processing
* items . Each received item is accumulator and it combines with previous value. Streams
* obtained accumulator on complete.
*
* @param <K> type of keys
* @param <I> type of input data
* @param <O> type of output data
* @param <A> type of accumulator
*/
public static final class AccumulatorToAccumulator<K, I, O, A> implements Reducer<K, A, A, A> {
private ReducerToResult<K, I, O, A> reducerToResult;
public AccumulatorToAccumulator(ReducerToResult<K, I, O, A> reducerToResult) {
this.reducerToResult = reducerToResult;
}
/**
* Creates accumulator which is first item
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
* @return accumulator with result
*/
@Override
public A onFirstItem(StreamDataReceiver<A> stream, K key, A firstValue) {
return firstValue;
}
/**
* Combines previous accumulator and each next item
*
* @param stream stream where to send result
* @param key key of element
* @param nextValue received value
* @param accumulator accumulator which contains results of all previous combining
* @return accumulator with result
*/
@Override
public A onNextItem(StreamDataReceiver<A> stream, K key, A nextValue, A accumulator) {
return reducerToResult.combine(accumulator, nextValue);
}
/**
* Streams obtained accumulator
*
* @param stream stream where to send result
* @param key key of element
* @param accumulator accumulator which contains results of all previous operations
*/
@Override
public void onComplete(StreamDataReceiver<A> stream, K key, A accumulator) {
stream.onData(accumulator);
}
}
}
/**
* Represents a reducer which streams accumulator
*
* @param <K> type of keys
* @param <I> type of input data
* @param <A> type of accumulator and type of output data
*/
public abstract static class ReducerToAccumulator<K, I, A> extends ReducerToResult<K, I, A, A> {
@Override
public final A produceResult(A accumulator) {
return accumulator;
}
}
/**
* Represents a reducer which deduplicates items with same keys. Streams only one item with
* each key
*
* @param <K> type of keys
* @param <T> type of input and output data
*/
public static class MergeDeduplicateReducer<K, T> implements Reducer<K, T, T, Void> {
/**
* On first item with new key it streams it
*
* @param stream stream where to send result
* @param key key of element
* @param firstValue received value
*/
@Override
public Void onFirstItem(StreamDataReceiver<T> stream, K key, T firstValue) {
stream.onData(firstValue);
return null;
}
@Override
public Void onNextItem(StreamDataReceiver<T> stream, K key, T nextValue, Void accumulator) {
return null;
}
@Override
public void onComplete(StreamDataReceiver<T> stream, K key, Void accumulator) {
}
}
/**
* Represent a reducer which streams received items sorted by keys
*
* @param <K> type of keys
* @param <T> type of input and output data
*/
public static class MergeSortReducer<K, T> implements Reducer<K, T, T, Void> {
@Override
public Void onFirstItem(StreamDataReceiver<T> stream, K key, T firstValue) {
stream.onData(firstValue);
return null;
}
@Override
public Void onNextItem(StreamDataReceiver<T> stream, K key, T nextValue, Void accumulator) {
stream.onData(nextValue);
return null;
}
@Override
public void onComplete(StreamDataReceiver<T> stream, K key, Void accumulator) {
}
}
}