Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.dm.jrt.core.Channels Maven / Gradle / Ivy
/*
* 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 com.github.dm.jrt.core;
import com.github.dm.jrt.channel.IOChannel;
import com.github.dm.jrt.channel.InputChannel;
import com.github.dm.jrt.channel.OutputChannel;
import com.github.dm.jrt.channel.OutputConsumer;
import com.github.dm.jrt.channel.RoutineException;
import com.github.dm.jrt.channel.StreamingChannel;
import com.github.dm.jrt.routine.Routine;
import com.github.dm.jrt.util.WeakIdentityHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Utility class for handling routine channels.
*
* Created by davide-maestroni on 03/15/2015.
*/
public class Channels {
private static final WeakIdentityHashMap, DefaultSelectableOutput>>
sSelectableOutputs =
new WeakIdentityHashMap, DefaultSelectableOutput>>();
/**
* Avoid direct instantiation.
*/
protected Channels() {
}
/**
* Creates and returns a new streaming channel by invoking the specified routine in asynchronous
* mode.
*
* @param routine the routine to be invoked.
* @param the input data type.
* @param the output data type.
* @return the streaming channel.
*/
@NotNull
public static StreamingChannel asyncStream(
@NotNull final Routine routine) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
return stream(ioChannel, routine.asyncCall(ioChannel));
}
/**
* Combines the specified channels into a selectable one. The selectable indexes will be the
* same as the array ones.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the array of input channels.
* @return the selectable I/O channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static IOChannel, Selectable>> combine(
@NotNull final InputChannel>... channels) {
return combine(0, channels);
}
/**
* Combines the specified channels into a selectable one.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param startIndex the selectable start index.
* @param channels the array of input channels.
* @return the selectable I/O channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
@SuppressWarnings("unchecked")
public static IOChannel, Selectable>> combine(final int startIndex,
@NotNull final InputChannel>... channels) {
final int length = channels.length;
if (length == 0) {
throw new IllegalArgumentException("the array of channels must not be empty");
}
final ArrayList> channelList = new ArrayList>(length);
for (final InputChannel> channel : channels) {
final IOChannel, ?> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo((InputChannel) channel);
channelList.add(ioChannel);
}
final IOChannel, Selectable>> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(new SortingInputConsumer(startIndex, channelList));
return ioChannel;
}
/**
* Combines the specified channels into a selectable one.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param startIndex the selectable start index.
* @param channels the list of input channels.
* @param the input data type.
* @return the selectable I/O channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
@SuppressWarnings("unchecked")
public static IOChannel, Selectable extends IN>> combine(
final int startIndex,
@NotNull final List extends InputChannel extends IN>> channels) {
if (channels.isEmpty()) {
throw new IllegalArgumentException("the list of channels must not be empty");
}
final ArrayList> channelList =
new ArrayList>(channels.size());
for (final InputChannel> channel : channels) {
final IOChannel, ?> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(((InputChannel) channel));
channelList.add(ioChannel);
}
final IOChannel, Selectable extends IN>> ioChannel =
JRoutine.io().buildChannel();
ioChannel.passTo(new SortingInputConsumer(startIndex, channelList));
return ioChannel;
}
/**
* Combines the specified channels into a selectable one. The selectable indexes will be the
* same as the list ones.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the list of input channels.
* @param the input data type.
* @return the selectable I/O channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static IOChannel, Selectable extends IN>> combine(
@NotNull final List extends InputChannel extends IN>> channels) {
return combine(0, channels);
}
/**
* Combines the specified channels into a selectable one.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the map of indexes and input channels.
* @param the input data type.
* @return the selectable I/O channel.
* @throws java.lang.IllegalArgumentException if the specified map is empty.
*/
@NotNull
@SuppressWarnings("unchecked")
public static IOChannel, Selectable extends IN>> combine(
@NotNull final Map> channels) {
if (channels.isEmpty()) {
throw new IllegalArgumentException("the map of channels must not be empty");
}
final HashMap> channelMap =
new HashMap>(channels.size());
for (final Entry> entry : channels.entrySet()) {
final IOChannel, ?> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(((InputChannel) entry.getValue()));
channelMap.put(entry.getKey(), ioChannel);
}
final IOChannel, Selectable extends IN>> ioChannel =
JRoutine.io().buildChannel();
ioChannel.passTo(new SortingInputMapConsumer(channelMap));
return ioChannel;
}
/**
* Returns a new channel distributing the input data among the specified channels. If the list
* of data exceeds the number of channels, the invocation will be aborted.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the array of channels.
* @return the I/O channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static IOChannel, List>> distribute(
@NotNull final InputChannel>... channels) {
return distribute(false, channels);
}
/**
* Returns a new channel distributing the input data among the specified channels. If the list
* of data exceeds the number of channels, the invocation will be aborted.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the list of channels.
* @param the input data type.
* @return the I/O channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static IOChannel, List extends IN>> distribute(
@NotNull final List extends InputChannel extends IN>> channels) {
return distribute(false, channels);
}
/**
* Returns a new channel distributing the input data among the specified channels. If the list
* of data is smaller of the specified number of channels, the remaining ones will be fed with
* null objects. While, if the list of data exceeds the number of channels, the invocation will
* be aborted.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the array of channels.
* @return the I/O channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static IOChannel, List>> distributeAndFlush(
@NotNull final InputChannel>... channels) {
return distribute(true, channels);
}
/**
* Returns a new channel distributing the input data among the specified channels. If the list
* of data is smaller of the specified number of channels, the remaining ones will be fed with
* null objects. While, if the list of data exceeds the number of channels, the invocation will
* be aborted.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channels the list of channels.
* @param the input data type.
* @return the I/O channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static IOChannel, List extends IN>> distributeAndFlush(
@NotNull final List extends InputChannel extends IN>> channels) {
return distribute(true, channels);
}
/**
* Returns an output channel joining the data coming from the specified list of channels.
* An output will be generated only when at least one result is available for each channel.
* Note that the channels will be bound as a result of the call.
*
* @param channels the list of channels.
* @param the output data type.
* @return the output channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static OutputChannel> join(
@NotNull final List extends OutputChannel extends OUT>> channels) {
return join(false, channels);
}
/**
* Returns an output channel joining the data coming from the specified list of channels.
* An output will be generated only when at least one result is available for each channel.
* Note that the channels will be bound as a result of the call.
*
* @param channels the array of channels.
* @return the output channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static OutputChannel> join(@NotNull final OutputChannel>... channels) {
return join(false, channels);
}
/**
* Returns an output channel joining the data coming from the specified list of channels.
* An output will be generated only when at least one result is available for each channel.
* Moreover, when all the output channels complete, the remaining output will be returned by
* filling the gaps with null instances, so that the generated list of data will always have the
* same size of the channel list.
* Note that the channels will be bound as a result of the call.
*
* @param channels the list of channels.
* @param the output data type.
* @return the output channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static OutputChannel> joinAndFlush(
@NotNull final List extends OutputChannel extends OUT>> channels) {
return join(true, channels);
}
/**
* Returns an output channel joining the data coming from the specified list of channels.
* An output will be generated only when at least one result is available for each channel.
* Moreover, when all the output channels complete, the remaining output will be returned by
* filling the gaps with null instances, so that the generated list of data will always have the
* same size of the channel array.
* Note that the channels will be bound as a result of the call.
*
* @param channels the array of channels.
* @return the output channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static OutputChannel> joinAndFlush(@NotNull final OutputChannel>... channels) {
return join(true, channels);
}
/**
* Merges the specified channels into a selectable one.
* Note that the channels will be bound as a result of the call.
*
* @param startIndex the selectable start index.
* @param channels the list of channels.
* @param the output data type.
* @return the selectable output channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static OutputChannel extends Selectable> merge(final int startIndex,
@NotNull final List extends OutputChannel extends OUT>> channels) {
if (channels.isEmpty()) {
throw new IllegalArgumentException("the list of channels must not be empty");
}
final IOChannel, Selectable> ioChannel = JRoutine.io().buildChannel();
int i = startIndex;
for (final OutputChannel extends OUT> channel : channels) {
ioChannel.pass(toSelectable(channel, i++));
}
return ioChannel.close();
}
/**
* Merges the specified channels into a selectable one.
* Note that the channels will be bound as a result of the call.
*
* @param startIndex the selectable start index.
* @param channels the array of channels.
* @return the selectable output channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static OutputChannel extends Selectable>> merge(final int startIndex,
@NotNull final OutputChannel>... channels) {
if (channels.length == 0) {
throw new IllegalArgumentException("the array of channels must not be empty");
}
final IOChannel, Selectable>> ioChannel = JRoutine.io().buildChannel();
int i = startIndex;
for (final OutputChannel> channel : channels) {
ioChannel.pass(toSelectable(channel, i++));
}
return ioChannel.close();
}
/**
* Merges the specified channels into a selectable one. The selectable indexes will be the same
* as the list ones.
* Note that the channels will be bound as a result of the call.
*
* @param channels the channels to merge.
* @param the output data type.
* @return the selectable output channel.
* @throws java.lang.IllegalArgumentException if the specified list is empty.
*/
@NotNull
public static OutputChannel extends Selectable> merge(
@NotNull final List extends OutputChannel extends OUT>> channels) {
return merge(0, channels);
}
/**
* Merges the specified channels into a selectable one.
* Note that the channels will be bound as a result of the call.
*
* @param channelMap the map of indexes and output channels.
* @param the output data type.
* @return the selectable output channel.
* @throws java.lang.IllegalArgumentException if the specified map is empty.
*/
@NotNull
public static OutputChannel extends Selectable> merge(
@NotNull final Map> channelMap) {
if (channelMap.isEmpty()) {
throw new IllegalArgumentException("the map of channels must not be empty");
}
final IOChannel, Selectable> ioChannel = JRoutine.io().buildChannel();
for (final Entry> entry : channelMap
.entrySet()) {
ioChannel.pass(toSelectable(entry.getValue(), entry.getKey()));
}
return ioChannel.close();
}
/**
* Merges the specified channels into a selectable one. The selectable indexes will be the same
* as the array ones.
* Note that the channels will be bound as a result of the call.
*
* @param channels the channels to merge.
* @return the selectable output channel.
* @throws java.lang.IllegalArgumentException if the specified array is empty.
*/
@NotNull
public static OutputChannel extends Selectable>> merge(
@NotNull final OutputChannel>... channels) {
return merge(0, channels);
}
/**
* Creates and returns a new streaming channel by invoking the specified routine in parallel
* mode.
*
* @param routine the routine to be invoked.
* @param the input data type.
* @param the output data type.
* @return the streaming channel.
*/
@NotNull
public static StreamingChannel parallelStream(
@NotNull final Routine routine) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
return stream(ioChannel, routine.parallelCall(ioChannel));
}
/**
* Returns a new channel transforming the input data into selectable ones.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channel the selectable channel.
* @param index the channel index.
* @param the channel data type.
* @param the input data type.
* @return the I/O channel.
*/
@NotNull
public static IOChannel select(
@Nullable final InputChannel super Selectable> channel, final int index) {
final IOChannel inputChannel = JRoutine.io().buildChannel();
if (channel != null) {
final IOChannel, Selectable> ioChannel =
JRoutine.io().buildChannel();
ioChannel.passTo(channel);
inputChannel.passTo(new SelectableInputConsumer(ioChannel, index));
}
return inputChannel;
}
/**
* Returns a map of input channels accepting the input data identified by the specified indexes.
*
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channel the selectable channel.
* @param indexes the iterable returning the channel indexes.
* @param the channel data type.
* @param the input data type.
* @return the map of indexes and I/O channels.
*/
@NotNull
public static Map> select(
@NotNull final InputChannel super Selectable> channel,
@NotNull final Iterable indexes) {
final HashMap> channelMap =
new HashMap>();
for (final Integer index : indexes) {
channelMap.put(index, Channels.select(channel, index));
}
return channelMap;
}
/**
* Returns a map of input channels accepting the input data identified by the specified indexes.
*
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channel the selectable channel.
* @param indexes the array of indexes.
* @param the channel data type.
* @param the input data type.
* @return the map of indexes and I/O channels.
*/
@NotNull
public static Map> select(
@NotNull final InputChannel super Selectable> channel,
@NotNull final int... indexes) {
final int size = indexes.length;
final HashMap> channelMap =
new HashMap>(size);
for (final int index : indexes) {
channelMap.put(index, Channels.select(channel, index));
}
return channelMap;
}
/**
* Returns a map of input channels accepting the input data identified by the specified indexes.
*
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param startIndex the selectable start index.
* @param rangeSize the size of the range of indexes (must be positive).
* @param channel the selectable channel.
* @param the channel data type.
* @param the input data type.
* @return the map of indexes and I/O channels.
* @throws java.lang.IllegalArgumentException if the specified range size is negative or 0.
*/
@NotNull
public static Map> select(
final int startIndex, final int rangeSize,
@NotNull final InputChannel super Selectable> channel) {
if (rangeSize <= 0) {
throw new IllegalArgumentException("invalid range size: " + rangeSize);
}
final HashMap> channelMap =
new HashMap>(rangeSize);
for (int index = startIndex; index < rangeSize; index++) {
channelMap.put(index, Channels.select(channel, index));
}
return channelMap;
}
/**
* Returns a map of output channels returning the output data filtered by the specified indexes.
*
* Note that the channel will be bound as a result of the call.
*
* @param startIndex the selectable start index.
* @param rangeSize the size of the range of indexes (must be positive).
* @param channel the selectable channel.
* @param the output data type.
* @return the map of indexes and output channels.
* @throws java.lang.IllegalArgumentException if the specified range size is negative or 0.
*/
@NotNull
public static Map> select(final int startIndex,
final int rangeSize,
@NotNull final OutputChannel extends Selectable extends OUT>> channel) {
if (rangeSize <= 0) {
throw new IllegalArgumentException("invalid range size: " + rangeSize);
}
final HashMap> inputMap =
new HashMap>(rangeSize);
final HashMap> outputMap =
new HashMap>(rangeSize);
for (int index = startIndex; index < rangeSize; index++) {
final Integer integer = index;
final IOChannel ioChannel = JRoutine.io().buildChannel();
inputMap.put(integer, ioChannel);
outputMap.put(integer, ioChannel);
}
channel.passTo(new SortingOutputMapConsumer(inputMap));
return outputMap;
}
/**
* Returns a selectable output filtering the data coming from the specified channel.
* Note that the channel will be bound as a result of the call, the method, though, can be
* safely called more than once for the same selectable channel.
*
* @param channel the selectable output channel.
* @param the output data type.
* @return the selectable output.
*/
@NotNull
@SuppressWarnings("unchecked")
public static SelectableOutput select(
@NotNull final OutputChannel extends Selectable extends OUT>> channel) {
synchronized (sSelectableOutputs) {
final WeakIdentityHashMap, DefaultSelectableOutput>>
selectableOutputs = sSelectableOutputs;
DefaultSelectableOutput> selectableOutput = selectableOutputs.get(channel);
if (selectableOutput == null) {
final DefaultSelectableOutput output = new DefaultSelectableOutput();
channel.passTo(output);
selectableOutputs.put(channel, output);
return output;
}
return (SelectableOutput) selectableOutput;
}
}
/**
* Returns a map of output channels returning the outputs filtered by the specified indexes.
*
* Note that the channel will be bound as a result of the call.
*
* @param channel the selectable output channel.
* @param indexes the list of indexes.
* @param the output data type.
* @return the map of indexes and output channels.
*/
@NotNull
public static Map> select(
@NotNull final OutputChannel extends Selectable extends OUT>> channel,
@NotNull final int... indexes) {
final int size = indexes.length;
final HashMap> inputMap =
new HashMap>(size);
final HashMap> outputMap =
new HashMap>(size);
for (final Integer index : indexes) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
inputMap.put(index, ioChannel);
outputMap.put(index, ioChannel);
}
channel.passTo(new SortingOutputMapConsumer(inputMap));
return outputMap;
}
/**
* Returns a map of output channels returning the output data filtered by the specified indexes.
*
* Note that the channel will be bound as a result of the call.
*
* @param channel the selectable output channel.
* @param indexes the iterable returning the channel indexes.
* @param the output data type.
* @return the map of indexes and output channels.
*/
@NotNull
public static Map> select(
@NotNull final OutputChannel extends Selectable extends OUT>> channel,
@NotNull final Iterable indexes) {
final HashMap> inputMap =
new HashMap>();
final HashMap> outputMap =
new HashMap>();
for (final Integer index : indexes) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
inputMap.put(index, ioChannel);
outputMap.put(index, ioChannel);
}
channel.passTo(new SortingOutputMapConsumer(inputMap));
return outputMap;
}
/**
* Creates and returns a new streaming channel backed by the specified input and output.
* Note that it is up to the caller ensure that the specified input and output channels are
* actually connected.
*
* @param inputChannel the input channel.
* @param outputChannel the output channel.
* @param the input data type.
* @param the output data type.
* @return the streaming channel.
*/
@NotNull
public static StreamingChannel stream(
@NotNull final IOChannel inputChannel,
@NotNull final OutputChannel outputChannel) {
return new DefaultStreamingChannel(inputChannel, outputChannel);
}
/**
* Creates and returns a new streaming channel by invoking the specified routine in synchronous
* mode.
*
* @param routine the routine to be invoked.
* @param the input data type.
* @param the output data type.
* @return the streaming channel.
*/
@NotNull
public static StreamingChannel syncStream(
@NotNull final Routine routine) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
return stream(ioChannel, routine.syncCall(ioChannel));
}
/**
* Returns a new selectable channel feeding the specified one.
* Each output will be filtered based on the specified index.
* Note that the returned channel must be explicitly closed in order to ensure the
* completion of the invocation lifecycle.
*
* @param channel the channel to make selectable.
* @param index the channel index.
* @param the input data type.
* @return the selectable I/O channel.
*/
@NotNull
public static IOChannel, Selectable> toSelectable(
@Nullable final InputChannel super IN> channel, final int index) {
final IOChannel, Selectable> inputChannel = JRoutine.io().buildChannel();
if (channel != null) {
final IOChannel ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(channel);
inputChannel.passTo(new FilterInputConsumer(ioChannel, index));
}
return inputChannel;
}
/**
* Returns a new channel making the specified one selectable.
* Each output will be passed along unchanged.
* Note that the channel will be bound as a result of the call.
*
* @param channel the channel to make selectable.
* @param index the channel index.
* @param the output data type.
* @return the selectable output channel.
*/
@NotNull
public static OutputChannel extends Selectable> toSelectable(
@Nullable final OutputChannel extends OUT> channel, final int index) {
final IOChannel, Selectable> ioChannel = JRoutine.io().buildChannel();
if (channel != null) {
channel.passTo(new SelectableOutputConsumer(ioChannel, index));
}
return ioChannel;
}
@NotNull
@SuppressWarnings("unchecked")
private static IOChannel, List>> distribute(final boolean isFlush,
@NotNull final InputChannel>... channels) {
final int length = channels.length;
if (length == 0) {
throw new IllegalArgumentException("the array of channels must not be empty");
}
final ArrayList> channelList = new ArrayList>(length);
for (final InputChannel> channel : channels) {
final IOChannel, ?> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(((InputChannel) channel));
channelList.add(ioChannel);
}
final IOChannel, List>> ioChannel = JRoutine.io().buildChannel();
return ioChannel.passTo(new DistributeInputConsumer(isFlush, channelList));
}
@NotNull
@SuppressWarnings("unchecked")
private static IOChannel, List extends IN>> distribute(
final boolean isFlush,
@NotNull final List extends InputChannel extends IN>> channels) {
if (channels.isEmpty()) {
throw new IllegalArgumentException("the list of channels must not be empty");
}
final ArrayList> channelList =
new ArrayList>(channels.size());
for (final InputChannel> channel : channels) {
final IOChannel, ?> ioChannel = JRoutine.io().buildChannel();
ioChannel.passTo(((InputChannel) channel));
channelList.add(ioChannel);
}
final IOChannel, List extends IN>> ioChannel =
JRoutine.io().buildChannel();
return ioChannel.passTo(new DistributeInputConsumer(isFlush, channelList));
}
@NotNull
private static OutputChannel> join(final boolean isFlush,
@NotNull final List extends OutputChannel extends OUT>> channels) {
final int size = channels.size();
if (size == 0) {
throw new IllegalArgumentException("the list of channels must not be empty");
}
final IOChannel, List extends OUT>> ioChannel =
JRoutine.io().buildChannel();
final JoinOutputConsumer consumer =
new JoinOutputConsumer(ioChannel, size, isFlush);
merge(channels).passTo(consumer);
return ioChannel;
}
@NotNull
@SuppressWarnings("unchecked")
private static OutputChannel> join(final boolean isFlush,
@NotNull final OutputChannel>... channels) {
final int length = channels.length;
if (length == 0) {
throw new IllegalArgumentException("the array of channels must not be empty");
}
final IOChannel, List>> ioChannel = JRoutine.io().buildChannel();
final JoinOutputConsumer consumer = new JoinOutputConsumer(ioChannel, length, isFlush);
merge(channels).passTo(consumer);
return ioChannel;
}
/**
* Interface defining a selectable output, that is an object filtering data coming from a
* selectable channel and dispatching them to a specific output channel based on their index.
*
* @param the output data type.
*/
public interface SelectableOutput {
/**
* Returns an output channel returning selectable data matching the specify index.
* New output channels can be bound until data start coming fro the selectable one. After
* that, any attempt to bound a channel to a new index will cause an exception to be thrown.
*
* @param index the channel index.
* @return the output channel.
* @throws java.lang.IllegalStateException if no more output channels can be bound to the
* output.
*/
@NotNull
OutputChannel index(int index);
}
/**
* Data class storing information about the origin of the data.
*
* @param the data type.
*/
public static class Selectable {
/**
* The data object.
*/
public final DATA data;
/**
* The origin channel index.
*/
public final int index;
/**
* Constructor.
*
* @param data the data object.
* @param index the channel index.
*/
public Selectable(final DATA data, final int index) {
this.data = data;
this.index = index;
}
/**
* Returns the data object casted to the specific type.
*
* @param the data type.
* @return the data object.
*/
@SuppressWarnings("unchecked")
public TYPE data() {
return (TYPE) data;
}
@Override
public int hashCode() {
// AUTO-GENERATED CODE
int result = data != null ? data.hashCode() : 0;
result = 31 * result + index;
return result;
}
@Override
public boolean equals(final Object o) {
// AUTO-GENERATED CODE
if (this == o) {
return true;
}
if (!(o instanceof Selectable)) {
return false;
}
final Selectable> that = (Selectable>) o;
return index == that.index && !(data != null ? !data.equals(that.data)
: that.data != null);
}
@Override
public String toString() {
// AUTO-GENERATED CODE
return "Selectable{" +
"data=" + data +
", index=" + index +
'}';
}
}
/**
* Default implementation of a selectable output.
*
* @param the output data type.
*/
private static class DefaultSelectableOutput
implements SelectableOutput, OutputConsumer> {
private final HashMap> mChannels =
new HashMap>();
private final Object mMutex = new Object();
private boolean mIsOutput;
@NotNull
public OutputChannel index(final int index) {
IOChannel channel;
synchronized (mMutex) {
final HashMap> channels = mChannels;
channel = channels.get(index);
if (channel == null) {
if (mIsOutput) {
throw new IllegalStateException();
}
channel = JRoutine.io().buildChannel();
channels.put(index, channel);
}
}
return channel;
}
public void onComplete() {
synchronized (mMutex) {
mIsOutput = true;
}
final HashMap> channels = mChannels;
for (final IOChannel channel : channels.values()) {
channel.close();
}
}
public void onOutput(final Selectable extends OUT> selectable) {
synchronized (mMutex) {
mIsOutput = true;
}
final HashMap> channels = mChannels;
final IOChannel channel = channels.get(selectable.index);
if (channel != null) {
channel.pass(selectable.data);
}
}
public void onError(@Nullable final RoutineException error) {
synchronized (mMutex) {
mIsOutput = true;
}
final HashMap> channels = mChannels;
for (final IOChannel channel : channels.values()) {
channel.abort(error);
}
}
}
/**
* Output consumer distributing list of inputs among a list of input channels.
*/
private static class DistributeInputConsumer implements OutputConsumer> {
private final ArrayList> mChannels;
private final boolean mIsFlush;
/**
* Constructor.
*
* @param isFlush whether the inputs have to be flushed.
* @param channels the list of channels.
*/
private DistributeInputConsumer(final boolean isFlush,
@NotNull final ArrayList> channels) {
mChannels = channels;
mIsFlush = isFlush;
}
public void onComplete() {
for (final IOChannel, ?> channel : mChannels) {
channel.close();
}
}
public void onError(@Nullable final RoutineException error) {
for (final IOChannel, ?> channel : mChannels) {
channel.abort(error);
}
}
@SuppressWarnings("unchecked")
public void onOutput(final List> inputs) {
final int inputSize = inputs.size();
final ArrayList> channels = mChannels;
final int size = channels.size();
if (inputSize > size) {
throw new IllegalArgumentException();
}
final boolean isFlush = mIsFlush;
for (int i = 0; i < size; ++i) {
final IOChannel channel =
(IOChannel) channels.get(i);
if (i < inputSize) {
channel.pass(inputs.get(i));
} else if (isFlush) {
channel.pass((Object) null);
}
}
}
}
/**
* Output consumer filtering selectable input data.
*
* @param the input data type.
*/
private static class FilterInputConsumer implements OutputConsumer> {
private final IOChannel super IN, ? super IN> mChannel;
private final int mIndex;
/**
* Constructor.
*
* @param channel the input channel to feed.
* @param index the index to filter.
*/
private FilterInputConsumer(@NotNull final IOChannel super IN, ? super IN> channel,
final int index) {
mChannel = channel;
mIndex = index;
}
public void onComplete() {
mChannel.close();
}
public void onError(@Nullable final RoutineException error) {
mChannel.abort(error);
}
public void onOutput(final Selectable selectable) {
if (selectable.index == mIndex) {
mChannel.pass(selectable.data);
}
}
}
/**
* Output consumer joining the data coming from several channels.
*
* @param the output data type.
*/
private static class JoinOutputConsumer implements OutputConsumer> {
private final IOChannel, List extends OUT>> mChannel;
private final boolean mIsFlush;
private final SimpleQueue[] mQueues;
/**
* Constructor.
*
* @param channel the I/O channel.
* @param size the number of channels to join.
* @param isFlush whether the inputs have to be flushed.
*/
@SuppressWarnings("unchecked")
private JoinOutputConsumer(
@NotNull final IOChannel, List extends OUT>> channel,
final int size, final boolean isFlush) {
final SimpleQueue[] queues = (mQueues = new SimpleQueue[size]);
mChannel = channel;
mIsFlush = isFlush;
for (int i = 0; i < size; ++i) {
queues[i] = new SimpleQueue();
}
}
protected void flush() {
final IOChannel, List extends OUT>> inputChannel = mChannel;
final SimpleQueue[] queues = mQueues;
final int length = queues.length;
final ArrayList outputs = new ArrayList(length);
boolean isEmpty;
do {
isEmpty = true;
for (final SimpleQueue queue : queues) {
if (!queue.isEmpty()) {
isEmpty = false;
outputs.add(queue.removeFirst());
} else {
outputs.add(null);
}
}
if (!isEmpty) {
inputChannel.pass(outputs);
outputs.clear();
} else {
break;
}
} while (true);
}
public void onComplete() {
if (mIsFlush) {
flush();
}
mChannel.close();
}
public void onError(@Nullable final RoutineException error) {
mChannel.abort(error);
}
@SuppressWarnings("unchecked")
public void onOutput(final Selectable selectable) {
final int index = selectable.index;
final SimpleQueue[] queues = mQueues;
queues[index].add(selectable.data);
final int length = queues.length;
boolean isFull = true;
for (final SimpleQueue queue : queues) {
if (queue.isEmpty()) {
isFull = false;
break;
}
}
if (isFull) {
final ArrayList outputs = new ArrayList(length);
for (final SimpleQueue queue : queues) {
outputs.add(queue.removeFirst());
}
mChannel.pass(outputs);
}
}
}
/**
* Output consumer transforming input data into selectable ones.
*
* @param the channel data type.
* @param the input data type.
*/
private static class SelectableInputConsumer
implements OutputConsumer {
private final IOChannel super Selectable, ? super Selectable> mChannel;
private final int mIndex;
/**
* Constructor.
*
* @param channel the selectable channel.
* @param index the selectable index.
*/
private SelectableInputConsumer(
@NotNull final IOChannel super Selectable, ? super Selectable>
channel,
final int index) {
mChannel = channel;
mIndex = index;
}
public void onComplete() {
mChannel.close();
}
public void onError(@Nullable final RoutineException error) {
mChannel.abort(error);
}
public void onOutput(final IN input) {
mChannel.pass(new Selectable(input, mIndex));
}
}
/**
* Output consumer transforming output data into selectable ones.
*
* @param the output data type.
*/
private static class SelectableOutputConsumer implements OutputConsumer {
private final IOChannel, Selectable> mChannel;
private final int mIndex;
/**
* Constructor.
*
* @param channel the I/O channel.
* @param index the selectable index.
*/
private SelectableOutputConsumer(
@NotNull final IOChannel, Selectable