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

com.github.dm.jrt.core.Channels Maven / Gradle / Ivy

There is a newer version: 5.9.0
Show newest version
/*
 * 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> combine( final int startIndex, @NotNull final List> 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> 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> combine( @NotNull final List> 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> 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> 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> distribute( @NotNull final List> 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> distributeAndFlush( @NotNull final List> 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> 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> 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> merge(final int startIndex, @NotNull final List> 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 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> 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> merge( @NotNull final List> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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 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> toSelectable( @Nullable final OutputChannel 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> distribute( final boolean isFlush, @NotNull final List> 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> ioChannel = JRoutine.io().buildChannel(); return ioChannel.passTo(new DistributeInputConsumer(isFlush, channelList)); } @NotNull private static OutputChannel> join(final boolean isFlush, @NotNull final List> channels) { final int size = channels.size(); if (size == 0) { throw new IllegalArgumentException("the list of channels must not be empty"); } final IOChannel, List> 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 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 mChannel; private final int mIndex; /** * Constructor. * * @param channel the input channel to feed. * @param index the index to filter. */ private FilterInputConsumer(@NotNull final IOChannel 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> 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> 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> 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> mChannel; private final int mIndex; /** * Constructor. * * @param channel the selectable channel. * @param index the selectable index. */ private SelectableInputConsumer( @NotNull final IOChannel, ? 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> 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 OUT output) { mChannel.pass(new Selectable(output, mIndex)); } } /** * Output consumer sorting selectable inputs among a list of input channels. */ private static class SortingInputConsumer implements OutputConsumer> { private final ArrayList> mChannelList; private final int mSize; private final int mStartIndex; /** * Constructor. * * @param startIndex the selectable start index. * @param channels the list of channels. */ private SortingInputConsumer(final int startIndex, @NotNull final ArrayList> channels) { mStartIndex = startIndex; mChannelList = channels; mSize = channels.size(); } public void onComplete() { for (final IOChannel inputChannel : mChannelList) { inputChannel.close(); } } public void onError(@Nullable final RoutineException error) { for (final IOChannel inputChannel : mChannelList) { inputChannel.abort(error); } } @SuppressWarnings("unchecked") public void onOutput(final Selectable selectable) { final int index = selectable.index - mStartIndex; if ((index < 0) || (index >= mSize)) { return; } final IOChannel inputChannel = (IOChannel) mChannelList.get(index); if (inputChannel != null) { inputChannel.pass(selectable.data); } } } /** * Output consumer sorting selectable inputs among a map of input channels. */ private static class SortingInputMapConsumer implements OutputConsumer> { private final HashMap> mChannels; /** * Constructor. * * @param channels the map of indexes and input channels. */ private SortingInputMapConsumer(@NotNull final HashMap> channels) { mChannels = channels; } public void onComplete() { for (final IOChannel inputChannel : mChannels.values()) { inputChannel.close(); } } public void onError(@Nullable final RoutineException error) { for (final IOChannel inputChannel : mChannels.values()) { inputChannel.abort(error); } } @SuppressWarnings("unchecked") public void onOutput(final Selectable selectable) { final IOChannel inputChannel = (IOChannel) mChannels.get(selectable.index); if (inputChannel != null) { inputChannel.pass(selectable.data); } } } /** * Output consumer sorting the output data among a map of output channels. * * @param the output data type. */ private static class SortingOutputMapConsumer implements OutputConsumer> { private final HashMap> mChannels; /** * Constructor. * * @param channels the map of indexes and I/O channels. */ private SortingOutputMapConsumer( @NotNull final HashMap> channels) { mChannels = channels; } public void onComplete() { for (final IOChannel channel : mChannels.values()) { channel.close(); } } public void onError(@Nullable final RoutineException error) { for (final IOChannel channel : mChannels.values()) { channel.abort(error); } } public void onOutput(final Selectable selectable) { final IOChannel channel = mChannels.get(selectable.index); if (channel != null) { channel.pass(selectable.data); } } } }