it.tidalwave.actor.impl.DefaultCollaboration.java Source code

Java tutorial

Introduction

Here is the source code for it.tidalwave.actor.impl.DefaultCollaboration.java

Source

/*
 * #%L
 * *********************************************************************************************************************
 * 
 * These Foolish Things - Miscellaneous utilities
 * http://thesefoolishthings.tidalwave.it - git clone git@bitbucket.org:tidalwave/thesefoolishthings-src.git
 * %%
 * Copyright (C) 2009 - 2018 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 * 
 * 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.
 * 
 * *********************************************************************************************************************
 * 
 * $Id$
 * 
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.actor.impl;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.io.Serializable;
import com.eaio.uuid.UUID;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import it.tidalwave.actor.Collaboration;
import it.tidalwave.actor.CollaborationCompletedMessage;
import it.tidalwave.actor.CollaborationStartedMessage;
import it.tidalwave.actor.annotation.Message;
import lombok.Getter;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import static lombok.AccessLevel.PRIVATE;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@RequiredArgsConstructor(access = PRIVATE)
@EqualsAndHashCode(of = "id")
@Slf4j
@ToString(of = "id")
public class DefaultCollaboration implements Serializable, Collaboration {
    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private final static DefaultCollaboration NULL_DEFAULT_COLLABORATION = new DefaultCollaboration(new Object()) {
        @Override
        public void bindToCurrentThread() {
        }

        @Override
        public void unbindFromCurrentThread() {
        }
    };

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    @RequiredArgsConstructor
    @ToString
    private static class IdentityWrapper {
        @Nonnull
        private final Object object;

        @Override
        public boolean equals(final Object object) {
            if ((object == null) || (getClass() != object.getClass())) {
                return false;
            }

            final IdentityWrapper other = (IdentityWrapper) object;
            return this.object == other.object;
        }

        @Override
        public int hashCode() {
            return object.hashCode();
        }
    }

    private final static ThreadLocal<DefaultCollaboration> THREAD_LOCAL = new ThreadLocal<DefaultCollaboration>();

    private final UUID id = new UUID();

    @Nonnull
    @Getter
    private final Object originatingMessage;

    private final long startTime = System.currentTimeMillis();

    @Getter
    private boolean completed;

    private final List<Object> suspensionTokens = new ArrayList<Object>();

    /** List of threads currently working for this Collaboration. */
    // No need for being weak, since objects are explicitly removed
    private final List<Thread> runningThreads = new ArrayList<Thread>();

    /** List of messages currently being delivered as part of this Collaboration. */
    // No need for being weak, since objects are explicitly removed
    private final List<Object> deliveringMessages = new ArrayList<Object>();

    /** List of messages pending to be consumed as part of this Collaboration. */
    // No need for being weak, since objects are explicitly removed
    private final List<IdentityWrapper> pendingMessages = new ArrayList<IdentityWrapper>();

    private boolean collaborationStartedMessageSent = false;

    /*******************************************************************************************************************
     *
     * Factory method to retrieve a {@link Collaboration}. This method accepts any object; if it is an implementation
     * of {@link Provider}, the object is queried; otherwise {@link #NULL_DEFAULT_COLLABORATION} is returned.
     *
     * @param   object    the object associated to the {@code Collaboration}
     * @return            the {@code Collaboration}
     *
     ******************************************************************************************************************/
    @Nonnull
    public static DefaultCollaboration getCollaboration(final @Nonnull Object object) {
        return (object instanceof Provider) ? (DefaultCollaboration) ((Provider) object).getCollaboration()
                : NULL_DEFAULT_COLLABORATION;
    }

    /*******************************************************************************************************************
     *
     * Gets the {@link Collaboration} bound to the current thread or creates a new one.
     *
     * @param   originator    the object that will be considered the originator of the {@code Collaboration} in case it
     *                        is created
     * @return                the {@code Collaboration}
     *
     ******************************************************************************************************************/
    @Nonnull
    public static DefaultCollaboration getOrCreateCollaboration(final @Nonnull Object originator) {
        DefaultCollaboration collaboration = THREAD_LOCAL.get();

        if (collaboration == null) {
            collaboration = new DefaultCollaboration(originator);
        }

        return collaboration;
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public DateTime getStartTime() {
        return new DateTime(startTime);
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public Duration getDuration() {
        return new Duration(startTime, System.currentTimeMillis());
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public synchronized void waitForCompletion() throws InterruptedException {
        while (!isCompleted()) {
            wait();
        }
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnegative
    public synchronized int getDeliveringMessagesCount() {
        return deliveringMessages.size();
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnegative
    public synchronized int getPendingMessagesCount() {
        return pendingMessages.size();
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnegative
    public synchronized int getRunningThreadsCount() {
        return runningThreads.size();
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void resume(final @Nonnull Object suspensionToken, final @Nonnull Runnable runnable) {
        try {
            bindToCurrentThread();
            runnable.run();
            suspensionTokens.remove(suspensionToken);
        } finally {
            unbindFromCurrentThread();
        }
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public synchronized void resumeAndDie(final @Nonnull Object suspensionToken) {
        resume(suspensionToken, new Runnable() {
            @Override
            public void run() {
            }
        });
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public synchronized Object suspend() {
        final Object suspensionToken = new UUID();
        suspensionTokens.add(suspensionToken);
        return suspensionToken;
    }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public synchronized boolean isSuspended() {
        return !suspensionTokens.isEmpty();
    }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    public synchronized void bindToCurrentThread() {
        log.trace("bindToCurrentThread()");
        THREAD_LOCAL.set(this);
        runningThreads.add(Thread.currentThread());
        notifyAll();
        log();
    }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    public synchronized void unbindFromCurrentThread() {
        log.trace("unbindFromCurrentThread()");
        runningThreads.remove(Thread.currentThread());
        THREAD_LOCAL.remove();
        notifyAll();
        log();
        eventuallySendCompletionMessage(null);
    }

    /*******************************************************************************************************************
     *
     * Registers that the given {@link Message} is being delivered.
     *
     * @param  message  the {@code Message}
     *
     ******************************************************************************************************************/
    public synchronized void registerDeliveringMessage(final @Nonnull Object message) {
        log.trace("registerDeliveringMessage({})", message);

        final Message annotation = message.getClass().getAnnotation(Message.class);

        if (annotation == null) {
            throw new IllegalArgumentException("Message must be annotated with @Message: " + message.getClass());
        }

        if (annotation.daemon()) {
            deliveringMessages.add(message);

            // Do this *after* enlisting message in deliveringMessages
            if (!collaborationStartedMessageSent && !(message instanceof CollaborationStartedMessage)) {
                CollaborationStartedMessage.forCollaboration(this).send();
                collaborationStartedMessageSent = true;
            }

            notifyAll();
            log();
        }
    }

    /*******************************************************************************************************************
     *
     * Registers that the given {@link Message} is no more being delivered.
     *
     * @param  message  the {@code Message}
     *
     ******************************************************************************************************************/
    public synchronized void unregisterDeliveringMessage(final @Nonnull Object message) {
        log.trace("unregisterDeliveringMessage({})", message);

        if (message.getClass().getAnnotation(Message.class).daemon()) {
            deliveringMessages.remove(message);
            notifyAll();
            log();
            eventuallySendCompletionMessage(message);
        }
    }

    /*******************************************************************************************************************
     *
     * Registers that the given {@link Message} is pending - this means it is in the recipient's queue, waiting to be
     * consumed.
     *
     * @param  message  the {@code Message}
     *
     ******************************************************************************************************************/
    public synchronized void registerPendingMessage(final @Nonnull Object message) {
        log.trace("registerPendingMessage({})", message);

        if (message.getClass().getAnnotation(Message.class).daemon()) {
            pendingMessages.add(new IdentityWrapper(message));
            notifyAll();
            log();
        }
    }

    /*******************************************************************************************************************
     *
     * Registers that the given {@link Message} is no more pending.
     *
     * @param  message  the {@code Message}
     *
     ******************************************************************************************************************/
    public synchronized void unregisterPendingMessage(final @Nonnull Object message) {
        log.trace("unregisterPendingMessage({})", message);

        if (message.getClass().getAnnotation(Message.class).daemon()) {
            pendingMessages.remove(new IdentityWrapper(message));
            notifyAll();
            log();
            eventuallySendCompletionMessage(message);
        }
    }

    /*******************************************************************************************************************
     *
     * If this {@link Collaboration} has been completed (that is, there are no more messages around for it), sends a
     * {@link CollaborationCompletedMessage}.
     *
     * @param  message  the {@code Message} - FIXME: seems to be useless
     *
     ******************************************************************************************************************/
    private void eventuallySendCompletionMessage(final Object message) {
        final int enqueuedMessageCount = deliveringMessages.size() + pendingMessages.size() + runningThreads.size()
                + suspensionTokens.size();

        if (!completed && (enqueuedMessageCount == 0)) {
            log.debug(">>>> sending completion message for {}", this);
            completed = true;
            THREAD_LOCAL.remove();
            CollaborationCompletedMessage.forCollaboration(this).send();
        }
    }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private void log() // FIXME: drop or move out of synchronized
    {
        //        log.trace("{}: delivering messages: {}, pending messages: {}, running threads: {}, suspension tokens: {}",
        //                  new Object[] {this, deliveringMessages.size(), pendingMessages.size(), runningThreads.size(), suspensionTokens.size()});
        //
        //        if (pendingMessages.size() < 2)
        //          {
        //            log.trace(">>>> pending messages: {}", pendingMessages);
        //          }
    }
}