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

com.ridgid.oss.queue.impl.inmemory.InMemoryMultiChannelFIFOQueue Maven / Gradle / Ivy

package com.ridgid.oss.queue.impl.inmemory;

import com.ridgid.oss.queue.spi.MultiChannelFIFOQueue;

import java.io.Serializable;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static java.lang.System.currentTimeMillis;
import static java.util.Comparator.comparingLong;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toConcurrentMap;

/**
 * In-Memory Implementation of a MultiChannelFIFOQueue
 *
 * @param  of messages sent and received through the queue
 */
@SuppressWarnings({"WeakerAccess", "NewMethodNamingConvention", "ClassNamePrefixedWithPackageName"})
public class InMemoryMultiChannelFIFOQueue
    implements MultiChannelFIFOQueue
{
    private final AtomicLong nextTimestamp = new AtomicLong(Long.MIN_VALUE);

    private final
    Class baseMessageType;

    private final
    ConcurrentMap, ConcurrentLinkedQueue>>
        queues;

    /**
     * Construct a single-channel queue where the single channel will handle all messages
     *
     * @param baseMessageType of the messages to send and receive through the queue
     */
    @SuppressWarnings("BoundedWildcard")
    public InMemoryMultiChannelFIFOQueue(Class baseMessageType) {
        this.baseMessageType = baseMessageType;
        queues               = makeEmptyConcurrentQueues(Stream.of(baseMessageType));
    }

    /**
     * Construct a multi-channel queue where all of the messages extend the base message type, but, there exists
     * multiple channels corresponding to the given list of message types
     *
     * @param baseMessageType of all messages in the queue across all channels
     * @param queues          of the messages for each channel
     */
    @SuppressWarnings("BoundedWildcard")
    public InMemoryMultiChannelFIFOQueue(Class baseMessageType,
                                         Collection> queues)
    {
        this.baseMessageType = baseMessageType;
        this.queues          = makeEmptyConcurrentQueues(queues.stream());
    }

    /**
     * Construct a multi-channel queue where all of the messages extend the base message type, but, there exists
     * multiple channels corresponding to the given list of message types
     *
     * @param baseMessageType of all messages in the queue across all channels
     * @param queues          of the messages for each channel
     */
    @SuppressWarnings({"OverloadedVarargsMethod", "BoundedWildcard"})
    @SafeVarargs
    public InMemoryMultiChannelFIFOQueue(Class baseMessageType,
                                         Class... queues)
    {
        this.baseMessageType = baseMessageType;
        this.queues          = makeEmptyConcurrentQueues(Arrays.stream(queues));
    }

    /**
     * Construct a multi-channel queue where all of the messages extend the base message type, but, there exists
     * multiple channels corresponding to the given list of message types
     *
     * @param baseMessageType of all messages in the queue across all channels
     * @param queues          of the messages for each channel
     */
    @SuppressWarnings("BoundedWildcard")
    public InMemoryMultiChannelFIFOQueue(Class baseMessageType,
                                         Stream> queues)
    {
        this.baseMessageType = baseMessageType;
        this.queues          = makeEmptyConcurrentQueues(queues);
    }

    private static 
    ConcurrentMap, ConcurrentLinkedQueue>>
    makeEmptyConcurrentQueues(Stream> queues)
    {
        return queues.collect(toConcurrentMap(identity(),
                                              InMemoryMultiChannelFIFOQueue::makeEmptyConcurrentQueue));
    }

    private static  ConcurrentLinkedQueue>
    makeEmptyConcurrentQueue(Class messageType)
    {
        return new ConcurrentLinkedQueue<>();
    }

    @Override
    public Class getBaseMessageType() {
        return baseMessageType;
    }

    @Override
    public Stream> streamChannelMessageTypes() {
        return queues.keySet().stream();
    }

    @Override
    public 
    Optional pollUnchecked(Class messageType,
                                                  long maxWaitMillis)
        throws MultiChannelFIFOQueueException
    {
        return Optional.ofNullable
            (
                waitForNextAvailable
                    (
                        messageType,
                        currentTimeMillis() + maxWaitMillis
                    )
            );
    }

    @SuppressWarnings("OverlyBroadCatchBlock")
    private 
    MessageType waitForNextAvailable(Class messageType,
                                     long endTimeMillis)
        throws MultiChannelFIFOQueueException
    {
        try {
            MessageType available;
            do available = nextAvailableMessageForRequestedMessageType(messageType);
            while
            (
                available == null
                &&
                waitingForMessagesUntil(endTimeMillis)
            );
            return available;
        } catch ( Exception e ) {
            throw new MultiChannelFIFOQueueException(e);
        }
    }

    @SuppressWarnings("NakedNotify")
    @Override
    public 
    void sendUnchecked(MessageType message)
        throws MultiChannelFIFOQueueException
    {
        queueMessageToMostAppropriateQueue(message);
        synchronized ( queues ) {
            queues.notifyAll();
        }
    }

    private 
    void queueMessageToMostAppropriateQueue(MessageType message)
        throws MultiChannelFIFOQueueException
    {
        Stream.concat(exactQueueForMessageType(message),
                      acceptableQueuesForMessageType(message))
              .findFirst()
              .map(Entry::getValue)
              .filter(messageQueued(message))
              .orElseThrow
                  (
                      () -> new MultiChannelFIFOQueueException
                          (
                              String.format("Message not supported on any channel: %s, %s",
                                            message.getClass().getName(),
                                            message)
                          )
                  );
    }

    private 
    Stream, ConcurrentLinkedQueue>>>
    acceptableQueuesForMessageType(MessageType message)
    {
        return queues
            .entrySet()
            .stream()
            .filter(entry -> entry.getKey().isAssignableFrom(message.getClass()));
    }

    private 
    Stream, ConcurrentLinkedQueue>>>
    exactQueueForMessageType(MessageType message)
    {
        return queues
            .entrySet()
            .stream()
            .filter(entry -> entry.getKey()
                                  .isAssignableFrom(message.getClass())
                             && message.getClass()
                                       .isAssignableFrom(entry.getKey()));
    }

    @SuppressWarnings("LocalVariableOfConcreteClass")
    private 
    Predicate>>
    messageQueued(MessageType message)
    {
        Timestamped msg = new Timestamped<>(message, nextTimestamp);
        return queue -> queue.offer(msg);
    }

    private 
    MessageType nextAvailableMessageForRequestedMessageType(Class messageType)
    {
        return
            nextMessageFromQueueOfExactMessageType(messageType)
                .map(msg -> (MessageType) msg)
                .orElseGet(() -> earliestMessageWhereSuperTypeIs(messageType));
    }

    @SuppressWarnings({"WaitNotInLoop", "OverlyNestedMethod", "BooleanMethodNameMustStartWithQuestion"})
    private 
    boolean waitingForMessagesUntil(long untilMillis)
    {
        long maxWaitMillis = untilMillis - currentTimeMillis();
        if ( maxWaitMillis > 0 )
            synchronized ( queues ) {
                try { queues.wait(maxWaitMillis); } catch ( InterruptedException ignore ) {}
            }
        return currentTimeMillis() < untilMillis;
    }

    @SuppressWarnings({"unchecked", "NewMethodNamingConvention"})
    private  Optional
    nextMessageFromQueueOfExactMessageType(Class messageType)
    {
        return Optional.ofNullable(queues.get(messageType))
                       .map(ConcurrentLinkedQueue::poll)
                       .map(Timestamped::unwrap)
                       .map(msg -> (MessageType) msg);
    }

    @SuppressWarnings("LocalVariableOfConcreteClass")
    private 
    MessageType earliestMessageWhereSuperTypeIs(Class messageType) {
        for
        (
            Entry, ConcurrentLinkedQueue>>
                nextApplicableQueue : headsOfQueuesSortedByFIFOOrder(messageType)
        ) {
            Timestamped val = nextApplicableQueue.getValue().poll();
            if ( val != null ) return messageType.cast(val.unwrap());
        }
        return null;
    }

    private 
    Iterable, ConcurrentLinkedQueue>>>
    headsOfQueuesSortedByFIFOOrder(Class messageType)
    {
        return
            queuesForBaseMessageType(messageType)
                .map(Entry::getValue)
                .map(toHeadOfQueueAndQueue())
                .filter(whereNonNullHeadOfQueue())
                .sorted(comparingLong(entry -> entry.getKey().getTimestamp()))
                ::iterator;
    }

    private 
    Stream, ConcurrentLinkedQueue>>>
    queuesForBaseMessageType(Class messageType)
    {
        return queues.entrySet()
                     .stream()
                     .filter(whereAssignableTo(messageType));
    }

    private Predicate, ConcurrentLinkedQueue>>>
    whereNonNullHeadOfQueue()
    {
        return entry -> Objects.nonNull(entry.getKey());
    }

    private 
    Predicate, ConcurrentLinkedQueue>>>
    whereAssignableTo(Class messageType)
    {
        return entry -> messageType.isAssignableFrom(entry.getKey());
    }

    private Function>, SimpleEntry, ConcurrentLinkedQueue>>>
    toHeadOfQueueAndQueue()
    {
        return queue -> new SimpleEntry<>(queue.peek(), queue);
    }

    /**
     * Note: this class has a natural ordering that is inconsistent with equals.
     */
    private static final class Timestamped
        implements Comparable>
    {
        private final T    obj;
        private final long timestamp;

        private Timestamped(T obj, AtomicLong nextTimestamp) {
            this.obj  = obj;
            timestamp = nextTimestamp.getAndIncrement();
        }

        public T unwrap() {
            return obj;
        }

        public long getTimestamp() {
            return timestamp;
        }

        @SuppressWarnings("MethodParameterOfConcreteClass")
        @Override
        public int compareTo(Timestamped o) {
            return Long.compare(timestamp, o.timestamp);
        }

        @Override
        public String toString() {
            return "Timestamped{" +
                   "obj=" + obj +
                   ", timestamp=" + timestamp +
                   '}';
        }

    }

    @Override
    public String toString() {
        return "InMemoryMultiChannelFIFOQueue{" +
               "nextTimestamp=" + nextTimestamp +
               ", baseMessageType=" + baseMessageType +
               ", queues=" + queues +
               '}';
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy