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

com.epam.deltix.data.stream.MessageSourceMultiplexer Maven / Gradle / Ivy

There is a newer version: 6.2.9
Show newest version
/*
 * Copyright 2024 EPAM Systems, Inc
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership. 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.epam.deltix.data.stream;

import com.epam.deltix.gflog.api.Log;
import com.epam.deltix.gflog.api.LogFactory;
import com.epam.deltix.gflog.api.LogLevel;
import com.epam.deltix.qsrv.hf.pub.*;
import com.epam.deltix.qsrv.hf.tickdb.impl.DebugFlags;
import com.epam.deltix.streaming.MessageSource;
import com.epam.deltix.timebase.messages.TimeStampedMessage;
import com.epam.deltix.timebase.messages.service.RealTimeStartMessage;
import com.epam.deltix.util.concurrent.AbstractCursor;
import com.epam.deltix.util.collections.generated.ObjectHashSet;
import com.epam.deltix.util.concurrent.*;
import net.jcip.annotations.GuardedBy;

import javax.annotation.Nonnull;
import java.util.*;

/**
 *  Merge multiple time-sorted message streams into one. Allows dynamic
 *  addition and removal of the source feeds.
 */
public class MessageSourceMultiplexer 
    implements RealTimeMessageSource
{
    public interface ExceptionHandler {
        /**
         *  Called when one of the multiplexed feeds throws an exception from the
         *  {@link AbstractCursor#next} method.
         *
         *  @param feed The feed that threw an exception.
         *  @param x    The exception.
         */
        void     nextThrewException (@Nonnull MessageSource feed, @Nonnull RuntimeException x);
    }

    static final Log LOGGER = LogFactory.getLog("deltix.tickdb.msm");

    static final boolean VALIDATE_LOCKS = Boolean.getBoolean("Timebase.MessageSourceMultiplexer.VALIDATE_LOCKS");

    //
    //  Immutable properties
    //
    private final boolean                       ascending;
    //
    //  Behavior control properties
    //
    private boolean                             allowLateOutOfOrder = true;
    private ExceptionHandler                    handler = null;
    private boolean                             live = false;
    //
    //  Essentially final members, but set to null in close ()
    //
    protected PriorityQueue                    queue;
    //
    //  Multiplexer state variables, guarded by "this"
    //
    private Runnable                            callerAvailLnr = null;
    protected MessageSource                    currentSource = null;
    protected long                                currentTime = Long.MIN_VALUE;
    protected T                                   currentMessage = null;
    protected boolean                             isAtBeginning = true; // TODO: This flag is not used. Remove.
    private boolean                             isAtEnd = false;

    protected ObjectHashSet>  emptySources = null;
    protected ArrayList >       checkSources = null;
    protected RuntimeException                    asyncException = null;

    private final ObjectHashSet> realTimeFeeds =
            new ObjectHashSet>();

    protected T                                   realtimeMessage = null;
    protected boolean                             isRealTime = false;
    protected boolean                             realTimeStarted = false;
    protected final boolean                       realTimeNotification;
    //private volatile int                        nonRealTimeFeeds = 0;

    //private final ObjectToObjectHashMap, String> last = new ObjectToObjectHashMap, String>();

    static final class TimeComparator implements Comparator {
        @Override
        public final int      compare(T o1, T o2) {
            long time1 = o1.getNanoTime();
            long time2 = o2.getNanoTime();

            // Unfortunately there is no easy way to simplify this. See Long.compare()
            return Long.compare(time1, time2);
        }
    }

    public MessageSourceMultiplexer () {
        this (true, false);
    }

    private MessageSourceMultiplexer (int capacity, boolean ascending, boolean realTimeNotification, Comparator c) {
        this.ascending = ascending;
        this.realTimeNotification = realTimeNotification;
        this.queue = new PriorityQueue<>(capacity, ascending, c);
    }

    public MessageSourceMultiplexer (boolean ascending, boolean realTimeNotification, Comparator c) {
        this(256, ascending, realTimeNotification, c);
    }
    
    public MessageSourceMultiplexer (boolean ascending, boolean realTimeNotification) {
        this(ascending, realTimeNotification, new TimeComparator<>());
    }

    @SuppressWarnings(value = {"unchecked", "varargs"})
    @SafeVarargs
    public MessageSourceMultiplexer (MessageSource  ... feeds) {
        this(feeds.length, true, false, new TimeComparator<>());
        reset (feeds);
    }
        
    public MessageSourceMultiplexer (List > feeds) {
        this(feeds.size(), true, false, new TimeComparator<>());
        reset (feeds);
    }

    public synchronized void        setAvailabilityListener (
        Runnable                        lnr
    )
    {
        callerAvailLnr = lnr;
    }

    public synchronized ExceptionHandler getExceptionHandler () {
        return handler;
    }

    public synchronized void        setExceptionHandler (ExceptionHandler handler) {
        this.handler = handler;
    }

    public synchronized boolean     isLive() {
        return live;
    }

    public synchronized void        setLive (boolean live) {
        this.live = live;
    }

    /**
     * If true, then MSM will expect real-time message (RealTimeStartMessage) from added sources and
     * will emit RealTimeStartMessage when all sources switched to the real-time mode only.
     * 
     * if true, all added messages sources expected to be {@link RealTimeMessageSource}
     * having realTimeAvailable() = true
     * 
     * @return true if MSM supports real-time mode.
     */

    @Override
    public boolean                  realTimeAvailable() {
        return realTimeNotification;
    }

    @Override
    public boolean                  isRealTime() {
        return isRealTime;
    }

    public synchronized boolean     isRealTimeStarted() {
        return isRealTime && realTimeStarted;
    }

    @SuppressWarnings("unchecked")
    public T                        createRealTimeMessage() {
        long time = currentTime != Long.MAX_VALUE ? currentTime : Long.MIN_VALUE;
        final RealTimeStartMessage msg = new RealTimeStartMessage();
        msg.setSymbol("");
        msg.setTimeStampMs(time);
        return (T) msg;             // Gene advised to always use native message. WAS: return null;  Bug#10343
    }
    
    public boolean                  isRealTimeMessage(T message) {
        if (message instanceof RawMessage)
            return ((RawMessage)message).type.getGuid().equals(RealTimeStartMessage.DESCRIPTOR_GUID);

        return message instanceof RealTimeStartMessage;
    }

    public synchronized boolean     getAllowLateOutOfOrder () {
        return allowLateOutOfOrder;
    }

    public synchronized void        setAllowLateOutOfOrder (boolean allowLateOutOfOrder) {
        this.allowLateOutOfOrder = allowLateOutOfOrder;
    }

    /**
     *  Removes the specified input feed from this multiplexer.
     *  If the cursor is not at beginning, and current feed is removed, 
     *  the current message becomes null. The cursor, however, is
     *  not advanced until {@link #next} is called.

     *  @param feed The feed to closeAndRemove.
     *  @return     If current message was removed.
     */
    public synchronized boolean     closeAndRemove (MessageSource  feed) {
        boolean     wasCurrent = remove(feed);

        feed.close();
        return (wasCurrent);
    }

    public synchronized boolean     remove (MessageSource  feed) {
        if (queue == null)
            return (false);
        
        boolean     wasCurrent = currentSource == feed;

        uninstallListener (feed);

        if (wasCurrent) {
            currentSource = null;
            currentMessage = null;
        }
        else if ((emptySources == null || !emptySources.remove (feed)))
            queue.remove (feed);

        if (checkSources != null)
            checkSources.remove(feed);

        if (feed instanceof RealTimeMessageSource)
            realTimeFeeds.remove((RealTimeMessageSource)feed);

        return (wasCurrent);
    }

    private void                    syncAddSourceNoNotify (MessageSource  feed) {
        assert Thread.holdsLock (this);

        if (checkSources == null)
            checkSources = new ArrayList > ();

        checkSources.add(feed);
    }

    private void                    addSourceAndNotify (MessageSource  feed) {
        //assert !Thread.holdsLock (this);
        /* can't assert this because TickCursorImpl locks this (pretty much illegally) but
         * takes counter-measures by making the avlnr asynchronous...
         * the correct fix is to fix sync patterns of tick cursor impl not to use
         * mx as a mutex.
         */

        Runnable    lnr = null;

        synchronized (this) {
            syncAddSourceNoNotify (feed);
            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }

    /**
     *  Called from the availability listener installed in all multiplexed
     *  feeds.
     * @param feed message source to check
     */
    private void                    checkDataAvailable (MessageSource  feed) {
        Runnable    lnr = null;

        synchronized (this) {
            if (emptySources != null && emptySources.remove (feed)) {
                syncAddSourceNoNotify (feed);
                lnr = syncNotify ();
            } else if (realTimeNotification) {
                // we waiting for this feed?
                if (feed instanceof RealTimeMessageSource &&
                        realTimeFeeds.contains((RealTimeMessageSource) feed))
                    notifyAll();
                else
                    lnr = syncNotify ();
            }
        }

        if (lnr != null)
            lnr.run ();
    }
    
    /**
     *  Adds a new feed without fast-forwarding it.
     *
     *  @param feed             The new feed
     */
    public void                     add (MessageSource  feed) {
        installListener (feed);
        addSourceAndNotify (feed);
    }

    /**
     *  Adds a new feed and fast-forwards it (rewinds if descending order),
     *  if necessary, until the specified timestamp, unless the
     *  allowLateOutOfOrder flag is not set. If the feed implements
     *  {@link IntermittentlyAvailableResource}, an availability listener is
     *  automatically installed in it.
     *
     *  @param feed              The new feed
     *  @param fastForwardToTime Scroll the feed to this timestamp.
     */
    public void                     add (MessageSource  feed, long fastForwardToTime) {
        Runnable        lnr;

        synchronized (this) {
            installListener (feed);
            addInternal (feed, fastForwardToTime);
            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }

    protected final void                    closeFeed (MessageSource  feed) {
        uninstallListener (feed);
        
        feed.close ();
    }

    protected void                  addEmptySource(MessageSource  feed) {
        if (emptySources == null)
            emptySources = new ObjectHashSet <> ();

        emptySources.add(feed);
    }

    /**
     * Returns true, if feed is closed.
     * @param feed
     * @param rtx
     * @return true, if feed is closed
     */
    protected final boolean                    handleException (
        MessageSource                feed,
        RuntimeException                rtx
    )
    {
        if (rtx.getClass () == UnavailableResourceException.class) {
            addEmptySource(feed);

            return false;
        }
        else if (handler == null) {
            asyncException = rtx;
            closeFeed (feed);

            return true;
        }
        else {
            try {
                handler.nextThrewException (feed, rtx);
                return false;
            } catch (RuntimeException x) {
                asyncException = x;
                closeFeed (feed);

                return true;
            }
        }
    }

    protected NextResult                 nextAvailable (RealTimeMessageSource feed) {
        for(;;) {
            synchronized (this) {

                if (queue == null)
                    throw new CursorIsClosedException ();
                
                NextResult next = moveNext (feed, false);

                if (next == NextResult.UNAVAILABLE) {
                    realTimeFeeds.add(feed);

                    try {
                        wait ();
                        continue;
                    } catch (InterruptedException e) {
                        throw new UncheckedInterruptedException (e);
                    }
                } else if (next == NextResult.OK && feed.isRealTime()) {

                    if (LOGGER.isEnabled(LogLevel.DEBUG))
                        LOGGER.debug(this + ": received real-time message " + feed.getMessage() + " from " + feed);

                    boolean hasReadTime = isRealTime;
                    if (realTimeFeeds.remove(feed) && verifyRealTimeMode()) {
                        if (!hasReadTime) {
                            if (LOGGER.isEnabled(LogLevel.DEBUG))
                                LOGGER.debug(this + " switched to real-time");

                            realtimeMessage = feed.getMessage();
                        }
                    }
                }

                return next;
            }
        }
    }

    protected NextResult        moveNext(MessageSource feed, boolean addEmpty) {
        try {
            if (feed.next ()) {
                return NextResult.OK;
            } else {
                closeFeed (feed);
                return NextResult.END_OF_CURSOR;
            }
        } catch (UnavailableResourceException x) {
            if (addEmpty)
                addEmptySource(feed);

            return NextResult.UNAVAILABLE;
        } catch (RuntimeException x) {
            if (handleException (feed, x))
                return NextResult.END_OF_CURSOR;

            return null;
        }
    }

    private NextResult advance (MessageSource  feed) {
        if (realTimeNotification) {

            if (feed instanceof RealTimeMessageSource) {
                NextResult result = advanceRealTime((RealTimeMessageSource) feed);
                if (result != null)
                    return result;
            }
        }
        
       return moveNext(feed, true);
    }

    protected final NextResult advanceRealTime(RealTimeMessageSource source) {
        //  if feed still not in real-time mode - advance with wait
        //  and check that source switched to the real-time mode
        if (source.realTimeAvailable()) {
            if (!source.isRealTime()) {

                // add source into waiting feeds
                //if (!realTimeFeeds.contains(source))
                //    realTimeFeeds.add(source);

                if (isRealTime) {
                    if (LOGGER.isEnabled(LogLevel.DEBUG))
                        LOGGER.debug(this + " advance() set " + source + " in real-time = false");
                    isRealTime = false;
                }

                NextResult result = nextAvailable(source);
                if (result != NextResult.OK)
                    return result;
                else if (!source.isRealTime())
                    return NextResult.OK;
            }

            // we should skip any RTSM in next
            for (;;) {
                NextResult result = moveNext(source, true);
                if (result == NextResult.OK) {
                    if (!isRealTimeMessage(source.getMessage()))
                        return NextResult.OK;
                } else {
                    return result;
                }
            }
        }
        // Fallback to the default flow
        return null;
    }

    @SuppressWarnings ("unchecked")
    private void                    installListener (final MessageSource  feed) {
        if (feed instanceof IntermittentlyAvailableResource) 
            ((IntermittentlyAvailableResource) feed).setAvailabilityListener (
                new Runnable () {
                    public void     run () {
                        checkDataAvailable (feed);                
                    }
                }
            );

        boolean supportsRealTime = false;

        if (realTimeNotification && feed instanceof RealTimeMessageSource) {
            RealTimeMessageSource source = (RealTimeMessageSource)feed;

            supportsRealTime = source.realTimeAvailable();
//            if (!supportsRealTime)
//                throw new IllegalArgumentException ("When realtime notifications is enabled all sources must support it.");

            if (supportsRealTime && !source.isRealTime()) {
                isRealTime = false;
                if (LOGGER.isEnabled(LogLevel.DEBUG)) {
                    LOGGER.debug(this + " installListener() set " + source + " in real-time = false");
                }
                if (!realTimeFeeds.contains(source)) {
                    realTimeFeeds.add(source);
                }
            }
        }

//        if (!supportsRealTime)
//            nonRealTimeFeeds++;
    }

    private boolean                 verifyRealTimeMode() {

        if (isRealTime)
            return true;

        isRealTime = realTimeNotification && realTimeFeeds.isEmpty(); // && nonRealTimeFeeds == 0;

        if (!isRealTime && LOGGER.isEnabled(LogLevel.DEBUG))
            LOGGER.debug(this + " verifyRealTimeMode() set real-time = false");

        return isRealTime;
    }

    private void                    uninstallListener (MessageSource  feed) {
        if (feed instanceof IntermittentlyAvailableResource)
            ((IntermittentlyAvailableResource) feed).setAvailabilityListener (null);

        if (realTimeNotification)
            if (feed instanceof RealTimeMessageSource && ((RealTimeMessageSource)feed).realTimeAvailable()) {
                if (realTimeFeeds.remove((RealTimeMessageSource)feed))
                    verifyRealTimeMode();
            }
//            else {
//                nonRealTimeFeeds--;
//            }
    }

    @GuardedBy("this")
    private Runnable                syncNotify () {
        assert Thread.holdsLock (this);

        if (callerAvailLnr == null) {
            notifyAll ();
            return (null);
        }
        else
            return (callerAvailLnr);
    }

    private void                    addInternal (MessageSource  feed, long fastForwardToTime) {

        while (advance (feed) == NextResult.OK) {
            if (!allowLateOutOfOrder) {
                TimeStampedMessage  msg = feed.getMessage ();
                long                ts = msg.getTimeStampMs ();

                if (ascending ? ts < fastForwardToTime : ts > fastForwardToTime) {
                    processDiscardedMessage(fastForwardToTime, msg);

                    continue;
                }
            }

            queue.offer (feed);
            break;
        }
    }

    private void processDiscardedMessage(long fastForwardToTime, TimeStampedMessage msg) {
        if (DebugFlags.DEBUG_MSG_DISCARD) {
            DebugFlags.discard (
                "TB DEBUG: Discarding " + msg +
                " while fast-forwarding to " + fastForwardToTime
            );
        }
    }

    protected final void                    addSync (MessageSource  feed, long fastForwardToTime) {
        addInternal(feed, fastForwardToTime);

        if (asyncException != null)
            throw asyncException;
    }

    private void                    addSync (MessageSource  feed) {
        if (advance (feed) == NextResult.OK)
            queue.offer (feed);
        else if (asyncException != null)
            throw asyncException;
    }

    public synchronized void        clearSources () {
        clearSourcesInternal ();
    }

    private void                    clearSourcesInternal () {
        if (currentSource != null)
            currentSource.close ();

        while (!queue.isEmpty ())
            queue.poll ().close ();

        if (emptySources != null) {
            for (MessageSource  feed : emptySources)
                closeFeed (feed);

            emptySources.clear ();
        }

        if (checkSources != null) {
            for (MessageSource  feed : checkSources)
                closeFeed (feed);

            checkSources.clear ();
        }

        // real-time related fields
        realTimeFeeds.clear();

        currentSource = null;
        currentMessage = null;
        isAtBeginning = true;
        isAtEnd = false;

        isRealTime = false;
        realtimeMessage = null;
        //nonRealTimeFeeds = 0;

        currentTime = Long.MIN_VALUE;
    }

    /**
     *  Call {@link #clearSources}, and then add the specified feeds.
     *
     *  @param feeds        The feeds to add.
     */
    @SuppressWarnings(value = {"unchecked", "varargs"})
    public void                 reset (MessageSource  ... feeds) {
        Runnable        lnr;

        synchronized (this) {
            clearSourcesInternal ();

            for (MessageSource  feed : feeds) {
                installListener (feed);
                syncAddSourceNoNotify (feed);
            }

            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }
        
    /**
     *  Call {@link #clearSources}, and then add the specified feeds.
     *
     *  @param feeds        The feeds to add.
     */
    public void                     reset (Collection > feeds) {
        Runnable        lnr;

        synchronized (this) {
            clearSourcesInternal ();

            for (MessageSource  feed : feeds) {
                installListener (feed);
                syncAddSourceNoNotify (feed);
            }

            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }
        
    /**
     *  Call {@link #clearSources}, and then add the specified feeds, fast-forwarding
     *  each until the timestamp of the current message from the feed is 
     *  at least startTime.
     * 
     *  @param startTime        Fast-forward to this time.
     *  @param feeds            The feeds to add.
     */
    @SuppressWarnings(value = {"unchecked", "varargs"})
    public void                 reset (long startTime, MessageSource  ... feeds) {
        Runnable        lnr;

        synchronized (this) {
            clearSourcesInternal ();

            currentTime = startTime;

            for (MessageSource  feed : feeds) {
                installListener (feed);
                addInternal (feed, startTime);
            }

            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }

    /**
     *  Call {@link #clearSources}, and set fast-forward time to the specified
     *  value.
     *
     *  @param startTime        Fast-forward to this time.
     */
    public synchronized void        reset (long startTime) {
        clearSourcesInternal ();
        currentTime = startTime;
    }

    /**
     *  Call {@link #clearSources}, and then add the specified feeds, fast-forwarding
     *  each until the timestamp of the current message from the feed is 
     *  at least startTime.
     * 
     *  @param startTime        Fast-forward to this time.
     *  @param feeds        The feeds to add.
     */
    public void                     reset (long startTime, Collection > feeds) {
        Runnable        lnr;

        synchronized (this) {
            clearSourcesInternal ();

            currentTime = startTime;

            for (MessageSource  feed : feeds) {
                installListener (feed);
                addInternal (feed, startTime);
            }

            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }

    public synchronized void        invalidateRealTimeState(RealTimeMessageSource source) {
        if (isRealTime)
            isRealTime = source.isRealTime();

        if (!source.isRealTime()) {
            if (emptySources != null && emptySources.contains(source)) {
                emptySources.remove(source);

                if (!checkSources.contains(source))
                    checkSources.add(source);
            }

            if (!realTimeFeeds.contains(source))
                realTimeFeeds.add(source);
        }
    }

    /**
     *  Asynchronous method which will cause next () to throw the specified 
     *  exception.
     *
     *  @param x
     */
    public void                     setException (RuntimeException x) {
        Runnable        lnr;

        synchronized (this) {
            asyncException = x;
            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }

    @Override
    public synchronized boolean     next () {
        return (syncNext ());
    }

    @GuardedBy("this")
    public boolean                  syncNext () {
        return syncNext(true) == NextResult.OK;
    }

    @GuardedBy("this")
    protected NextResult                  syncNext (boolean throwable) {
        assert !VALIDATE_LOCKS || Thread.holdsLock (this);
        
        for (;;) {
            //
            //  This checks for feed exception caught prior to next (), or
            //  asynchronously in the availability listener while this thread
            //  was in wait ().
            //
            if (asyncException != null)
                throw asyncException;

            if (queue == null)
                throw new CursorIsClosedException ();
            //
            //  Re-offer last used feed
            //
            if (isAtBeginning) {
                isAtBeginning = false;
            } else if (currentSource != null) {
                // Main path for historic data
                addSync(currentSource);
            }

            currentSource = null;
            currentMessage = null;
            
            realTimeStarted = false;
            //
            //  Re-check newly available sources
            //
            if (checkSources != null) {
                // TODO: Extract this loop into separate method
                for (;;) {
                    int         n = checkSources.size ();

                    if (n == 0)
                        break;

                    addSync (checkSources.remove (n - 1), currentTime);                    
                }
            }

            if (queue.isEmpty ()) {
                NextResult x = processEmptyQueue(throwable);
                if (x != null) {
                    return x;
                }
            }
            else {
                if (isRealTime && realtimeMessage != null) {
                    sendRealTimeMessage(" send real-time message: ");
                }
                else {
                    // Main path for historic data
                    currentSource = queue.poll();
                    assert currentSource != null;

//                    String previous = last.get(currentSource, null);
//                    if (previous != null && previous.equals(currentSource.getMessage().toString())) {
//                        System.out.println("------------" + this + ": Found possible dub from: " +  currentSource  + " = " + currentSource.getMessage());
//                    }
//                    last.put(currentSource, currentMessage.toString());

                    currentMessage = currentSource.getMessage();
                    currentTime = currentMessage.getTimeStampMs();
                }

                return NextResult.OK;
            }            
        }      
    }

    @GuardedBy("this")
    protected final NextResult processEmptyQueue(boolean throwable) {
        assert Thread.holdsLock (this);

        boolean isEmpty = isEmpty();

        if (!live && isEmpty) {
            isAtEnd = true;
            return NextResult.END_OF_CURSOR;
        }
        else if (realtimeMessage != null) {

            assert isRealTimeMessage(realtimeMessage);
            assert (isRealTime);

            sendRealTimeMessage(" send real-time message: ");
            return NextResult.OK;
        }
        else if (realTimeNotification && isEmpty && !isRealTime) {
            isRealTime = true;
            sendRealTimeMessage(" send new real-time message: ");
            return NextResult.OK;
        }
        else if (callerAvailLnr != null) {
            if (throwable)
                throw UnavailableResourceException.INSTANCE;

            return NextResult.UNAVAILABLE;
        } else {
            try {
                wait ();
                return null;
            } catch (InterruptedException x) {
                throw new UncheckedInterruptedException(x);
            }
        }
    }

    protected final void sendRealTimeMessage(String logText) {
        currentMessage = createRealTimeMessage();
        currentSource = null;
        realtimeMessage = null;
        realTimeStarted = true;

        if (LOGGER.isEnabled(LogLevel.DEBUG))
            LOGGER.debug(this + logText + currentMessage);
    }

    protected boolean              isEmpty() {
        return emptySources == null || emptySources.isEmpty();
    }

    public final synchronized boolean isAtEnd () {
        return (isAtEnd);
    }

    public final synchronized T     getMessage () {
        return (currentMessage);        
    }

    public final synchronized long  getCurrentTime () {
        return (currentTime);
    }
    
    public synchronized MessageSource  getCurrentSource () {
        return currentSource;
    }

    public synchronized boolean     isClosed () {
        return (queue == null);
    }

    @GuardedBy("this")
    public final boolean            syncIsAtEnd () {
        assert Thread.holdsLock (this);
        return (isAtEnd);
    }

    @GuardedBy("this")
    public final T                  syncGetMessage () {
        assert Thread.holdsLock (this);
        return (currentMessage);
    }

    @GuardedBy("this")
    public final long               syncGetCurrentTime () {
        assert Thread.holdsLock (this);
        return (currentTime);
    }

    @GuardedBy("this")
    public MessageSource         syncGetCurrentSource () {
        assert Thread.holdsLock (this);
        return currentSource;
    }

    @GuardedBy("this")
    public boolean                  syncIsClosed () {
        assert Thread.holdsLock (this);
        return (queue == null);
    }

    public void                     close () {
        Runnable        lnr;

        synchronized (this) {
            if (queue == null)
                return;

            //  Close everything
            clearSourcesInternal ();

            queue = null;
            emptySources = null;

            lnr = syncNotify ();
        }

        if (lnr != null)
            lnr.run ();
    }    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy