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

com.gh.bmd.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.gh.bmd.jrt.core;

import com.gh.bmd.jrt.channel.InputChannel;
import com.gh.bmd.jrt.channel.OutputChannel;
import com.gh.bmd.jrt.channel.OutputConsumer;
import com.gh.bmd.jrt.channel.RoutineException;
import com.gh.bmd.jrt.channel.TemplateOutputConsumer;
import com.gh.bmd.jrt.channel.TransportChannel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Utility class for handling routine channels.
 * 

* Created by davide-maestroni on 3/15/15. */ public class Channels { /** * Avoid direct instantiation. */ protected Channels() { } /** * Combines the specified channels into a selectable one. The selectable indexes will be the * same as the list ones. * * @param channels the array of input channels. * @return the selectable input channel. * @throws java.lang.IllegalArgumentException if the specified array is empty. */ @Nonnull public static InputChannel> combine(@Nonnull final InputChannel... channels) { return combine(0, channels); } /** * Combines the specified channels into a selectable one. * * @param startIndex the selectable start index. * @param channels the array of input channels. * @return the selectable input channel. * @throws java.lang.IllegalArgumentException if the specified array is empty. */ @Nonnull public static InputChannel> combine(final int startIndex, @Nonnull 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); Collections.addAll(channelList, channels); final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); transportChannel.passTo(new SortingInputConsumer(startIndex, channelList)); return transportChannel; } /** * Combines the specified channels into a selectable one. * * @param startIndex the selectable start index. * @param channels the list of input channels. * @return the selectable input channel. * @throws java.lang.IllegalArgumentException if the specified list is empty. */ @Nonnull public static InputChannel> combine(final int startIndex, @Nonnull final List> channels) { if (channels.isEmpty()) { throw new IllegalArgumentException("the list of channels must not be empty"); } final ArrayList> channelList = new ArrayList>(channels); final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); transportChannel.passTo(new SortingInputConsumer(startIndex, channelList)); return transportChannel; } /** * Combines the specified channels into a selectable one. The selectable indexes will be the * same as the list ones. * * @param channels the list of input channels. * @return the selectable input channel. * @throws java.lang.IllegalArgumentException if the specified list is empty. */ @Nonnull public static InputChannel> combine( @Nonnull final List> channels) { return combine(0, channels); } /** * Combines the specified channels into a selectable one. * * @param channels the map of indexes and input channels. * @return the selectable input channel. * @throws java.lang.IllegalArgumentException if the specified map is empty. */ @Nonnull public static InputChannel> combine( @Nonnull final Map> channels) { if (channels.isEmpty()) { throw new IllegalArgumentException("the map of channels must not be empty"); } final HashMap> channelMap = new HashMap>(channels); final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); transportChannel.passTo(new SortingInputMapConsumer(channelMap)); return transportChannel; } /** * 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. * * @param channels the array of channels. * @return the input channel. * @throws java.lang.IllegalArgumentException if the specified array is empty. */ @Nonnull public static InputChannel> distribute(@Nonnull 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. * * @param channels the list of channels. * @return the input channel. * @throws java.lang.IllegalArgumentException if the specified list is empty. */ @Nonnull public static InputChannel> distribute( @Nonnull 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. * * @param channels the array of channels. * @return the input channel. * @throws java.lang.IllegalArgumentException if the specified array is empty. */ @Nonnull public static InputChannel> distributeAndFlush( @Nonnull 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. * * @param channels the list of channels. * @return the input channel. * @throws java.lang.IllegalArgumentException if the specified list is empty. */ @Nonnull public static InputChannel> distributeAndFlush( @Nonnull 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. */ @Nonnull public static OutputChannel> join( @Nonnull 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. */ @Nonnull public static OutputChannel> join(@Nonnull 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. */ @Nonnull public static OutputChannel> joinAndFlush( @Nonnull 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. */ @Nonnull public static OutputChannel> joinAndFlush(@Nonnull final OutputChannel... channels) { return join(true, channels); } /** * Returns a map of input channels accepting the input data identified by the specified indexes. * * @param channel the selectable channel. * @param indexes the collection of indexes. * @param the channel data type. * @param the input data type. * @return the map of indexes and output channels. */ @Nonnull public static Map> map( @Nonnull final InputChannel> channel, @Nonnull final Collection indexes) { final int size = indexes.size(); final HashMap> channelMap = new HashMap>(size); 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. * * @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 output channels. */ @Nonnull public static Map> map( @Nonnull final InputChannel> channel, @Nonnull 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. * * @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 output channels. * @throws java.lang.IllegalArgumentException if the specified range size is negative or 0. */ @Nonnull public static Map> map( final int startIndex, final int rangeSize, @Nonnull 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. */ @Nonnull public static Map> map(final int startIndex, final int rangeSize, @Nonnull 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 TransportChannel transportChannel = JRoutine.transport().buildChannel(); inputMap.put(integer, transportChannel); outputMap.put(integer, transportChannel); } channel.passTo(new SortingOutputConsumer(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 list of indexes. * @param the output data type. * @return the channel map. */ @Nonnull public static Map> map( @Nonnull final OutputChannel> channel, @Nonnull final Collection indexes) { final int size = indexes.size(); final HashMap> inputMap = new HashMap>(size); final HashMap> outputMap = new HashMap>(size); for (final Integer index : indexes) { final TransportChannel transportChannel = JRoutine.transport().buildChannel(); inputMap.put(index, transportChannel); outputMap.put(index, transportChannel); } channel.passTo(new SortingOutputConsumer(inputMap)); return outputMap; } /** * 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 channel map. */ @Nonnull public static Map> map( @Nonnull final OutputChannel> channel, @Nonnull 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 TransportChannel transportChannel = JRoutine.transport().buildChannel(); inputMap.put(index, transportChannel); outputMap.put(index, transportChannel); } channel.passTo(new SortingOutputConsumer(inputMap)); return outputMap; } /** * 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. */ @Nonnull public static OutputChannel> merge(final int startIndex, @Nonnull final List> channels) { if (channels.isEmpty()) { throw new IllegalArgumentException("the list of channels must not be empty"); } final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); int i = startIndex; for (final OutputChannel channel : channels) { transportChannel.pass(toSelectable(channel, i++)); } return transportChannel.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. */ @Nonnull public static OutputChannel> merge(final int startIndex, @Nonnull final OutputChannel... channels) { if (channels.length == 0) { throw new IllegalArgumentException("the array of channels must not be empty"); } final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); int i = startIndex; for (final OutputChannel channel : channels) { transportChannel.pass(toSelectable(channel, i++)); } return transportChannel.close(); } /** * Merges the specified channels into a selectable one. *

* 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. */ @Nonnull public static OutputChannel> merge( @Nonnull 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. */ @Nonnull public static OutputChannel> merge( @Nonnull final Map> channelMap) { if (channelMap.isEmpty()) { throw new IllegalArgumentException("the map of channels must not be empty"); } final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); for (final Entry> entry : channelMap .entrySet()) { transportChannel.pass(toSelectable(entry.getValue(), entry.getKey())); } return transportChannel.close(); } /** * Merges the specified channels into a selectable one. *

* 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. */ @Nonnull public static OutputChannel> merge( @Nonnull final OutputChannel... channels) { return merge(0, channels); } /** * Returns a new channel transforming the input data into selectable ones. * * @param channel the selectable channel. * @param index the channel index. * @param the channel data type. * @param the input data type. * @return the input channel. */ @Nonnull public static InputChannel select( @Nullable final InputChannel> channel, final int index) { final TransportChannel transportChannel = JRoutine.transport().buildChannel(); if (channel != null) { transportChannel.passTo(new SelectableInputConsumer(channel, index)); } return transportChannel; } /** * Returns a new channel transforming the output data into selectable ones. *

* Note that the channel will be bound as a result of the call. * * @param channel the selectable channel. * @param index the channel index. * @param the output data type. * @return the output channel. */ @Nonnull public static OutputChannel select( @Nullable final OutputChannel> channel, final int index) { final TransportChannel transportChannel = JRoutine.transport().buildChannel(); if (channel != null) { channel.passTo(new FilterOutputConsumer(transportChannel, index)); } return transportChannel; } /** * Returns a new selectable channel feeding the specified one.
* Each output will be filtered based on the specified index. * * @param channel the channel to make selectable. * @param index the channel index. * @param the input data type. * @return the selectable input channel. */ @Nonnull public static InputChannel> toSelectable( @Nullable final InputChannel channel, final int index) { final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); if (channel != null) { transportChannel.passTo(new FilterInputConsumer(channel, index)); } return transportChannel; } /** * 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. */ @Nonnull public static OutputChannel> toSelectable( @Nullable final OutputChannel channel, final int index) { final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); if (channel != null) { channel.passTo(new SelectableOutputConsumer(transportChannel, index)); } return transportChannel; } @Nonnull private static InputChannel> distribute(final boolean isFlush, @Nonnull 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); Collections.addAll(channelList, channels); final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); return transportChannel.passTo(new DistributeInputConsumer(isFlush, channelList)); } @Nonnull private static InputChannel> distribute(final boolean isFlush, @Nonnull final List> channels) { if (channels.isEmpty()) { throw new IllegalArgumentException("the list of channels must not be empty"); } final ArrayList> channelList = new ArrayList>(channels); final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); return transportChannel.passTo(new DistributeInputConsumer(isFlush, channelList)); } @Nonnull private static OutputChannel> join(final boolean isFlush, @Nonnull final List> channels) { final int size = channels.size(); if (size == 0) { throw new IllegalArgumentException("the list of channels must not be empty"); } final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); final JoinOutputConsumer consumer = new JoinOutputConsumer(transportChannel, size, isFlush); merge(channels).passTo(consumer); return transportChannel; } @Nonnull @SuppressWarnings("unchecked") private static OutputChannel> join(final boolean isFlush, @Nonnull final OutputChannel... channels) { final int length = channels.length; if (length == 0) { throw new IllegalArgumentException("the array of channels must not be empty"); } final TransportChannel> transportChannel = JRoutine.transport().buildChannel(); final JoinOutputConsumer consumer = new JoinOutputConsumer(transportChannel, length, isFlush); merge(channels).passTo(consumer); return transportChannel; } /** * Data class storing information about the origin of the data. * * @param the data type. */ @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "this is an immutable data class") 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); } } /** * Output consumer distributing list of inputs among a list of input channels. */ private static class DistributeInputConsumer extends TemplateOutputConsumer> { 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, @Nonnull final ArrayList> channels) { mChannels = channels; mIsFlush = isFlush; } @Override public void onError(@Nullable final RoutineException error) { for (final InputChannel channel : mChannels) { channel.abort(error); } } @Override @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 InputChannel channel = (InputChannel) 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 extends TemplateOutputConsumer> { private final int mIndex; private final InputChannel mInputChannel; /** * Constructor. * * @param inputChannel the input channel to feed. * @param index the index to filter. */ private FilterInputConsumer(@Nonnull final InputChannel inputChannel, final int index) { mInputChannel = inputChannel; mIndex = index; } @Override public void onError(@Nullable final RoutineException error) { mInputChannel.abort(error); } @Override public void onOutput(final Selectable selectable) { if (selectable.index == mIndex) { mInputChannel.pass(selectable.data); } } } /** * Output consumer filtering selectable output data. * * @param the output data type. */ private static class FilterOutputConsumer implements OutputConsumer> { private final int mIndex; private final TransportChannel mInputChannel; /** * Constructor. * * @param inputChannel the transport channel. * @param index the index to filter. */ private FilterOutputConsumer(@Nonnull final TransportChannel inputChannel, final int index) { mInputChannel = inputChannel; mIndex = index; } public void onComplete() { mInputChannel.close(); } public void onError(@Nullable final RoutineException error) { mInputChannel.abort(error); } public void onOutput(final Selectable selectable) { if (selectable.index == mIndex) { mInputChannel.pass(selectable.data); } } } /** * Output consumer joining the data coming from several channels. * * @param the output data type. */ private static class JoinOutputConsumer implements OutputConsumer> { protected final TransportChannel> mInputChannel; protected final SimpleQueue[] mQueues; private final boolean mIsFlush; /** * Constructor. * * @param inputChannel the transport channel. * @param size the number of channels to join. * @param isFlush whether the inputs have to be flushed. */ @SuppressWarnings("unchecked") private JoinOutputConsumer(@Nonnull final TransportChannel> inputChannel, final int size, final boolean isFlush) { final SimpleQueue[] queues = (mQueues = new SimpleQueue[size]); mInputChannel = inputChannel; mIsFlush = isFlush; for (int i = 0; i < size; i++) { queues[i] = new SimpleQueue(); } } protected void flush() { final TransportChannel> inputChannel = mInputChannel; 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(); } mInputChannel.close(); } public void onError(@Nullable final RoutineException error) { mInputChannel.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()); } mInputChannel.pass(outputs); } } } /** * Output consumer transforming input data into selectable ones. * * @param the channel data type. * @param the input data type. */ private static class SelectableInputConsumer extends TemplateOutputConsumer { private final int mIndex; private final InputChannel> mInputChannel; /** * Constructor. * * @param inputChannel the selectable channel. * @param index the selectable index. */ private SelectableInputConsumer( @Nonnull final InputChannel> inputChannel, final int index) { mInputChannel = inputChannel; mIndex = index; } @Override public void onError(@Nullable final RoutineException error) { mInputChannel.abort(error); } @Override public void onOutput(final INPUT input) { mInputChannel.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 int mIndex; private final TransportChannel> mInputChannel; /** * Constructor. * * @param inputChannel the transport channel. * @param index the selectable index. */ private SelectableOutputConsumer( @Nonnull final TransportChannel> inputChannel, final int index) { mInputChannel = inputChannel; mIndex = index; } public void onComplete() { mInputChannel.close(); } public void onError(@Nullable final RoutineException error) { mInputChannel.abort(error); } public void onOutput(final OUTPUT output) { mInputChannel.pass(new Selectable(output, mIndex)); } } /** * Output consumer sorting selectable inputs among a list of input channels. */ private static class SortingInputConsumer extends TemplateOutputConsumer> { 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, @Nonnull final ArrayList> channels) { mStartIndex = startIndex; mChannelList = channels; mSize = channels.size(); } @Override public void onError(@Nullable final RoutineException error) { for (final InputChannel inputChannel : mChannelList) { inputChannel.abort(error); } } @Override @SuppressWarnings("unchecked") public void onOutput(final Selectable selectable) { final int index = selectable.index - mStartIndex; if ((index < 0) || (index >= mSize)) { return; } final InputChannel inputChannel = (InputChannel) 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 extends TemplateOutputConsumer> { private final HashMap> mChannelMap; /** * Constructor. * * @param channelMap the map of indexes and input channels. */ private SortingInputMapConsumer( @Nonnull final HashMap> channelMap) { mChannelMap = channelMap; } @Override public void onError(@Nullable final RoutineException error) { for (final InputChannel inputChannel : mChannelMap.values()) { inputChannel.abort(error); } } @Override @SuppressWarnings("unchecked") public void onOutput(final Selectable selectable) { final InputChannel inputChannel = (InputChannel) mChannelMap.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 SortingOutputConsumer implements OutputConsumer> { private final HashMap> mChannelMap; /** * Constructor. * * @param channelMap the map of indexes and transport channels. */ private SortingOutputConsumer( @Nonnull final HashMap> channelMap) { mChannelMap = channelMap; } public void onComplete() { for (final TransportChannel channel : mChannelMap.values()) { channel.close(); } } public void onError(@Nullable final RoutineException error) { for (final TransportChannel channel : mChannelMap.values()) { channel.abort(error); } } public void onOutput(final Selectable selectable) { final TransportChannel channel = mChannelMap.get(selectable.index); if (channel != null) { channel.pass(selectable.data); } } } }