WIP - polishing
This commit is contained in:
parent
3226d8ae20
commit
d31e27eadd
|
@ -1,7 +1,7 @@
|
||||||
MessageBus
|
MessageBus
|
||||||
==========
|
==========
|
||||||
|
|
||||||
The MessageBus is a fork from MBassador, and it is a high-performance, very-low GC, custom distribution that supports method signature
|
The MessageBus is a fork from MBassador, and it is a high-performance, zero GC, custom distribution that supports method signature
|
||||||
parameters > 1 & varity arguments.
|
parameters > 1 & varity arguments.
|
||||||
|
|
||||||
*Many* features from the original MBassador have been removed, specifically the ONLY things to remain for a handler are
|
*Many* features from the original MBassador have been removed, specifically the ONLY things to remain for a handler are
|
||||||
|
@ -10,7 +10,8 @@ parameters > 1 & varity arguments.
|
||||||
|
|
||||||
Additionally, the bus *must* explicitly be started now (because of errorhandling when starting the disruptor),
|
Additionally, the bus *must* explicitly be started now (because of errorhandling when starting the disruptor),
|
||||||
ie: ```.start()```, and conversely ```.shutdown()``` is necessary to shutdown the disruptor/thread pool. During the distillation
|
ie: ```.start()```, and conversely ```.shutdown()``` is necessary to shutdown the disruptor/thread pool. During the distillation
|
||||||
process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```.
|
process, the API has changed, and the only way to publish now is to actually call ```bus.publish()``` or ```bus.publishAsync()```, of
|
||||||
|
note, the asynchronous publication of messages is not in a guaranteed order.
|
||||||
|
|
||||||
|
|
||||||
The largest change however, is the ability to publish N-number of objects. A single object (or all-matching-types, when more than one)
|
The largest change however, is the ability to publish N-number of objects. A single object (or all-matching-types, when more than one)
|
||||||
|
|
|
@ -109,12 +109,6 @@ public interface IMessageBus extends PubSubSupport {
|
||||||
* Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
|
* Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
|
||||||
*/
|
*/
|
||||||
ExactWithSuperTypes,
|
ExactWithSuperTypes,
|
||||||
|
|
||||||
/**
|
|
||||||
* Will publish to listeners with this exact message signature, as well as listeners that match the super class types signatures.
|
|
||||||
* and to listeners that have matching varity arguments. (ie: a listener that matches Object[], will accept messages of type Object)
|
|
||||||
*/
|
|
||||||
ExactWithSuperTypesAndVarity,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,8 +20,9 @@ import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
import dorkbox.util.messagebus.publication.Publisher;
|
||||||
import dorkbox.util.messagebus.publication.PublisherExact;
|
import dorkbox.util.messagebus.publication.PublisherExact;
|
||||||
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes;
|
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypes;
|
||||||
import dorkbox.util.messagebus.publication.PublisherExactWithSuperTypesAndVarity;
|
|
||||||
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
|
import dorkbox.util.messagebus.synchrony.AsyncABQ;
|
||||||
|
import dorkbox.util.messagebus.synchrony.AsyncABQ_noGc;
|
||||||
import dorkbox.util.messagebus.synchrony.AsyncDisruptor;
|
import dorkbox.util.messagebus.synchrony.AsyncDisruptor;
|
||||||
import dorkbox.util.messagebus.synchrony.Sync;
|
import dorkbox.util.messagebus.synchrony.Sync;
|
||||||
import dorkbox.util.messagebus.synchrony.Synchrony;
|
import dorkbox.util.messagebus.synchrony.Synchrony;
|
||||||
|
@ -37,6 +38,36 @@ import dorkbox.util.messagebus.synchrony.Synchrony;
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class MessageBus implements IMessageBus {
|
class MessageBus implements IMessageBus {
|
||||||
|
|
||||||
|
public static boolean useDisruptorForAsyncPublish = true;
|
||||||
|
public static boolean useAsmForDispatch = true;
|
||||||
|
|
||||||
|
public static boolean useNoGarbageVersionOfABQ = true;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// check to see if we can use ASM for method access (it's a LOT faster than reflection). By default, we use ASM.
|
||||||
|
if (useAsmForDispatch) {
|
||||||
|
// only bother checking if we are different that the defaults
|
||||||
|
try {
|
||||||
|
Class.forName("com.esotericsoftware.reflectasm.MethodAccess");
|
||||||
|
} catch (Exception e) {
|
||||||
|
useAsmForDispatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check to see if we can use the disruptor for publication (otherwise, we use native java). The disruptor is a lot faster, but
|
||||||
|
// not available on all platforms/JRE's because of it's use of UNSAFE.
|
||||||
|
if (useDisruptorForAsyncPublish) {
|
||||||
|
// only bother checking if we are different that the defaults
|
||||||
|
try {
|
||||||
|
Class.forName("com.lmax.disruptor.RingBuffer");
|
||||||
|
} catch (Exception e) {
|
||||||
|
useDisruptorForAsyncPublish = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final ErrorHandlingSupport errorHandler;
|
private final ErrorHandlingSupport errorHandler;
|
||||||
|
|
||||||
private final SubscriptionManager subscriptionManager;
|
private final SubscriptionManager subscriptionManager;
|
||||||
|
@ -47,7 +78,7 @@ class MessageBus implements IMessageBus {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, will permit subTypes and Varity Argument matching, and will use half of CPUs available for dispatching async messages
|
* By default, will permit subType matching, and will use half of CPUs available for dispatching async messages
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
MessageBus() {
|
MessageBus() {
|
||||||
|
@ -55,7 +86,7 @@ class MessageBus implements IMessageBus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, will permit subTypes and Varity Argument matching
|
* By default, will permit subType matching
|
||||||
*
|
*
|
||||||
* @param numberOfThreads how many threads to use for dispatching async messages
|
* @param numberOfThreads how many threads to use for dispatching async messages
|
||||||
*/
|
*/
|
||||||
|
@ -74,6 +105,7 @@ class MessageBus implements IMessageBus {
|
||||||
MessageBus(final PublishMode publishMode) {
|
MessageBus(final PublishMode publishMode) {
|
||||||
this(publishMode, Runtime.getRuntime().availableProcessors());
|
this(publishMode, Runtime.getRuntime().availableProcessors());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param publishMode Specifies which publishMode to operate the publication of messages.
|
* @param publishMode Specifies which publishMode to operate the publication of messages.
|
||||||
* @param numberOfThreads how many threads to use for dispatching async messages
|
* @param numberOfThreads how many threads to use for dispatching async messages
|
||||||
|
@ -96,17 +128,26 @@ class MessageBus implements IMessageBus {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ExactWithSuperTypes:
|
case ExactWithSuperTypes:
|
||||||
|
default:
|
||||||
publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager);
|
publisher = new PublisherExactWithSuperTypes(errorHandler, subscriptionManager);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ExactWithSuperTypesAndVarity:
|
|
||||||
default:
|
|
||||||
publisher = new PublisherExactWithSuperTypesAndVarity(errorHandler, subscriptionManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
syncPublication = new Sync();
|
syncPublication = new Sync();
|
||||||
// asyncPublication = new PubAsync(numberOfThreads, errorHandler, publisher, syncPublication);
|
|
||||||
asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication);
|
// the disruptor is preferred, but if it cannot be loaded -- we want to try to continue working, hence the use of ArrayBlockingQueue
|
||||||
|
if (useDisruptorForAsyncPublish) {
|
||||||
|
asyncPublication = new AsyncDisruptor(numberOfThreads, errorHandler, publisher, syncPublication);
|
||||||
|
} else {
|
||||||
|
if (useNoGarbageVersionOfABQ) {
|
||||||
|
// no garbage is created, but this is slow (but faster than other messagebus implementations)
|
||||||
|
asyncPublication = new AsyncABQ_noGc(numberOfThreads, errorHandler, publisher, syncPublication);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// garbage is created, but this is fast
|
||||||
|
asyncPublication = new AsyncABQ(numberOfThreads, errorHandler, publisher, syncPublication);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,12 +201,6 @@ class MessageBus implements IMessageBus {
|
||||||
publisher.publish(syncPublication, message1, message2, message3);
|
publisher.publish(syncPublication, message1, message2, message3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Object[] messages) {
|
|
||||||
publisher.publish(syncPublication, messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void publishAsync(final Object message) {
|
void publishAsync(final Object message) {
|
||||||
|
@ -204,22 +239,6 @@ class MessageBus implements IMessageBus {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publishAsync(final Object[] messages) {
|
|
||||||
// if (messages != null) {
|
|
||||||
// try {
|
|
||||||
// this.dispatchQueue.transfer(messages, MessageType.ARRAY);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Error while adding an asynchronous message").setCause(e).setPublishedObject(messages));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// throw new NullPointerException("Message cannot be null.");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
boolean hasPendingMessages() {
|
boolean hasPendingMessages() {
|
||||||
|
|
|
@ -1,776 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.publication.disruptor.MessageType;
|
|
||||||
import org.jctools.queues.MpmcArrayQueue;
|
|
||||||
import org.jctools.util.UnsafeAccess;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import static org.jctools.util.UnsafeRefArrayAccess.lpElement;
|
|
||||||
import static org.jctools.util.UnsafeRefArrayAccess.spElement;
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
final
|
|
||||||
class MpmcMultiTransferArrayQueue extends MpmcArrayQueue<Object> {
|
|
||||||
private static final int TYPE_EMPTY = 0;
|
|
||||||
private static final int TYPE_CONSUMER = 1;
|
|
||||||
private static final int TYPE_PRODUCER = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is it multi-processor?
|
|
||||||
*/
|
|
||||||
private static final boolean MP = Runtime.getRuntime().availableProcessors() > 1;
|
|
||||||
|
|
||||||
private static final int INPROGRESS_SPINS = MP ? 32 : 0;
|
|
||||||
private static final int PRODUCER_CAS_FAIL_SPINS = MP ? 512 : 0;
|
|
||||||
private static final int CONSUMER_CAS_FAIL_SPINS = MP ? 512 : 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of times to spin before blocking in timed waits.
|
|
||||||
* The value is empirically derived -- it works well across a
|
|
||||||
* variety of processors and OSes. Empirically, the best value
|
|
||||||
* seems not to vary with number of CPUs (beyond 2) so is just
|
|
||||||
* a constant.
|
|
||||||
*/
|
|
||||||
private static final int PARK_TIMED_SPINS = MP ? 32 : 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of times to spin before blocking in untimed waits.
|
|
||||||
* This is greater than timed value because untimed waits spin
|
|
||||||
* faster since they don't need to check times on each spin.
|
|
||||||
*/
|
|
||||||
private static final int PARK_UNTIMED_SPINS = PARK_TIMED_SPINS * 16;
|
|
||||||
|
|
||||||
private static final ThreadLocal<Object> nodeThreadLocal = new ThreadLocal<Object>() {
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Object initialValue() {
|
|
||||||
return new MultiNode();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final ThreadLocal<Random> randomThreadLocal = new ThreadLocal<Random>() {
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Random initialValue() {
|
|
||||||
return new Random();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final int consumerCount;
|
|
||||||
|
|
||||||
public
|
|
||||||
MpmcMultiTransferArrayQueue(final int consumerCount) {
|
|
||||||
super(1024); // must be power of 2
|
|
||||||
this.consumerCount = consumerCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PRODUCER method
|
|
||||||
* <p>
|
|
||||||
* Place an item on the queue, and wait as long as necessary for a corresponding consumer to take it.
|
|
||||||
* <p>
|
|
||||||
* The item can be a single object (MessageType.ONE), or an array object (MessageType.ARRAY)
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
void transfer(final Object item, final int messageType) throws InterruptedException {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = MultiNode.lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastType != TYPE_CONSUMER) {
|
|
||||||
// TYPE_EMPTY, TYPE_PRODUCER
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
final long newProducerIndex = producerIndex + 1;
|
|
||||||
if (casProducerIndex(producerIndex, newProducerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
|
||||||
final Object node = nodeThreadLocal.get();
|
|
||||||
|
|
||||||
MultiNode.spType(node, TYPE_PRODUCER);
|
|
||||||
MultiNode.spThread(node, myThread);
|
|
||||||
|
|
||||||
MultiNode.spMessageType(node, messageType);
|
|
||||||
MultiNode.spItem1(node, item);
|
|
||||||
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(buffer, offset, node);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
|
|
||||||
|
|
||||||
park(node, myThread);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(PRODUCER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TYPE_CONSUMER
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long newConsumerIndex = consumerIndex + 1;
|
|
||||||
final long delta = seq - newConsumerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElement(buffer, offset);
|
|
||||||
spElement(buffer, offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
|
|
||||||
|
|
||||||
MultiNode.spMessageType(e, messageType);
|
|
||||||
MultiNode.spItem1(e, item);
|
|
||||||
|
|
||||||
unpark(e); // StoreStore
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(CONSUMER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PRODUCER method
|
|
||||||
* <p/>
|
|
||||||
* Place two items in the same slot on the queue, and wait as long as necessary for a corresponding consumer to take it.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
void transfer(final Object item1, final Object item2) throws InterruptedException {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = MultiNode.lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastType != TYPE_CONSUMER) {
|
|
||||||
// TYPE_EMPTY, TYPE_PRODUCER
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
final long newProducerIndex = producerIndex + 1;
|
|
||||||
if (casProducerIndex(producerIndex, newProducerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
|
||||||
final Object node = nodeThreadLocal.get();
|
|
||||||
|
|
||||||
MultiNode.spType(node, TYPE_PRODUCER);
|
|
||||||
MultiNode.spThread(node, myThread);
|
|
||||||
|
|
||||||
MultiNode.spMessageType(node, MessageType.TWO);
|
|
||||||
MultiNode.spItem1(node, item1);
|
|
||||||
MultiNode.spItem2(node, item2);
|
|
||||||
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(buffer, offset, node);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
|
|
||||||
|
|
||||||
park(node, myThread);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(PRODUCER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TYPE_CONSUMER
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long newConsumerIndex = consumerIndex + 1;
|
|
||||||
final long delta = seq - newConsumerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElement(buffer, offset);
|
|
||||||
spElement(buffer, offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
|
|
||||||
|
|
||||||
MultiNode.spMessageType(e, MessageType.TWO);
|
|
||||||
MultiNode.spItem1(e, item1);
|
|
||||||
MultiNode.spItem2(e, item2);
|
|
||||||
|
|
||||||
unpark(e); // StoreStore
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(CONSUMER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PRODUCER method
|
|
||||||
* <p/>
|
|
||||||
* Place three items in the same slot on the queue, and wait as long as necessary for a corresponding consumer to take it.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
void transfer(final Object item1, final Object item2, final Object item3) throws InterruptedException {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = MultiNode.lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastType != TYPE_CONSUMER) {
|
|
||||||
// TYPE_EMPTY, TYPE_PRODUCER
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
final long newProducerIndex = producerIndex + 1;
|
|
||||||
if (casProducerIndex(producerIndex, newProducerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
|
||||||
final Object node = nodeThreadLocal.get();
|
|
||||||
|
|
||||||
MultiNode.spType(node, TYPE_PRODUCER);
|
|
||||||
MultiNode.spThread(node, myThread);
|
|
||||||
|
|
||||||
MultiNode.spMessageType(node, MessageType.THREE);
|
|
||||||
MultiNode.spItem1(node, item1);
|
|
||||||
MultiNode.spItem2(node, item2);
|
|
||||||
MultiNode.spItem3(node, item3);
|
|
||||||
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(buffer, offset, node);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
|
|
||||||
|
|
||||||
park(node, myThread);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(PRODUCER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TYPE_CONSUMER
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long newConsumerIndex = consumerIndex + 1;
|
|
||||||
final long delta = seq - newConsumerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElement(buffer, offset);
|
|
||||||
spElement(buffer, offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
|
|
||||||
|
|
||||||
MultiNode.spMessageType(e, MessageType.THREE);
|
|
||||||
MultiNode.spItem1(e, item1);
|
|
||||||
MultiNode.spItem2(e, item2);
|
|
||||||
MultiNode.spItem3(e, item3);
|
|
||||||
|
|
||||||
unpark(e); // StoreStore
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(CONSUMER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CONSUMER
|
|
||||||
* <p/>
|
|
||||||
* Remove an item from the queue. If there are no items on the queue, wait for a producer to place an item on the queue. This will
|
|
||||||
* as long as necessary.
|
|
||||||
* <p/>
|
|
||||||
* This method does not depend on thread-local for node information, and so is more efficient.
|
|
||||||
* <p/>
|
|
||||||
* Also, the node used by this method will contain the data.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
void take(final MultiNode node) throws InterruptedException {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = MultiNode.lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastType != TYPE_PRODUCER) {
|
|
||||||
// TYPE_EMPTY, TYPE_CONSUMER
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
final long newProducerIndex = producerIndex + 1;
|
|
||||||
if (casProducerIndex(producerIndex, newProducerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
|
||||||
// final Object node = nodeThreadLocal.publish();
|
|
||||||
|
|
||||||
MultiNode.spType(node, TYPE_CONSUMER);
|
|
||||||
MultiNode.spThread(node, myThread);
|
|
||||||
|
|
||||||
// The unpark thread sets our contents
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(buffer, offset, node);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
|
|
||||||
|
|
||||||
park(node, myThread);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(PRODUCER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TYPE_PRODUCER
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long newConsumerIndex = consumerIndex + 1;
|
|
||||||
final long delta = seq - newConsumerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElement(buffer, offset);
|
|
||||||
spElement(buffer, offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
|
|
||||||
|
|
||||||
MultiNode.spMessageType(node, MultiNode.lvMessageType(e)); // LoadLoad
|
|
||||||
MultiNode.spItem1(node, MultiNode.lpItem1(e));
|
|
||||||
MultiNode.spItem2(node, MultiNode.lpItem2(e));
|
|
||||||
MultiNode.spItem3(node, MultiNode.lpItem3(e));
|
|
||||||
|
|
||||||
unpark(e);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(CONSUMER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// modification of super implementation, as to include a small busySpin on contention
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean offer(Object item) {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final long capacity = mask + 1;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long producerIndex;
|
|
||||||
long pSeqOffset;
|
|
||||||
long consumerIndex = Long.MAX_VALUE;// start with bogus value, hope we don't need it
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
producerIndex = lvProducerIndex(); // LoadLoad
|
|
||||||
|
|
||||||
pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
final long newProducerIndex = producerIndex + 1;
|
|
||||||
if (casProducerIndex(producerIndex, newProducerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(buffer, offset, item);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, newProducerIndex); // StoreStore
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(PRODUCER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
// failed cas, retry 1
|
|
||||||
}
|
|
||||||
else if (delta < 0 && // poll has not moved this value forward
|
|
||||||
producerIndex - capacity <= consumerIndex && // test against cached cIndex
|
|
||||||
producerIndex - capacity <= (consumerIndex = lvConsumerIndex())) { // test against latest cIndex
|
|
||||||
// Extra check required to ensure [Queue.offer == false iff queue is full]
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// another producer has moved the sequence by one, retry 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// modification of super implementation, as to include a small busySpin on contention
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object poll() {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long cSeqOffset;
|
|
||||||
long producerIndex = -1; // start with bogus value, hope we don't need it
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex(); // LoadLoad
|
|
||||||
cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long newConsumerIndex = consumerIndex + 1;
|
|
||||||
final long delta = seq - newConsumerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, newConsumerIndex)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElement(buffer, offset);
|
|
||||||
spElement(buffer, offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, mask + newConsumerIndex); // StoreStore
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
busySpin(CONSUMER_CAS_FAIL_SPINS);
|
|
||||||
}
|
|
||||||
// failed cas, retry 1
|
|
||||||
}
|
|
||||||
else if (delta < 0 && // slot has not been moved by producer
|
|
||||||
consumerIndex >= producerIndex && // test against cached pIndex
|
|
||||||
consumerIndex == (producerIndex = lvProducerIndex())) { // update pIndex if we must
|
|
||||||
// strict empty check, this ensures [Queue.poll() == null iff isEmpty()]
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// another consumer beat us and moved sequence ahead, retry 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean isEmpty() {
|
|
||||||
// Order matters!
|
|
||||||
// Loading consumer before producer allows for producer increments after consumer index is read.
|
|
||||||
// This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is
|
|
||||||
// nothing we can do to make this an exact method.
|
|
||||||
return lvConsumerIndex() == lvProducerIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object peek() {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
|
|
||||||
long currConsumerIndex;
|
|
||||||
Object e;
|
|
||||||
do {
|
|
||||||
currConsumerIndex = lvConsumerIndex();
|
|
||||||
// other consumers may have grabbed the element, or queue might be empty
|
|
||||||
e = lpElement(buffer, calcElementOffset(currConsumerIndex, mask));
|
|
||||||
// only return null if queue is empty
|
|
||||||
} while (e == null && currConsumerIndex != lvProducerIndex());
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
int size() {
|
|
||||||
/*
|
|
||||||
* It is possible for a thread to be interrupted or reschedule between the read of the producer and
|
|
||||||
* consumer indices, therefore protection is required to ensure size is within valid range. In the
|
|
||||||
* event of concurrent polls/offers to this method the size is OVER estimated as we read consumer
|
|
||||||
* index BEFORE the producer index.
|
|
||||||
*/
|
|
||||||
long after = lvConsumerIndex();
|
|
||||||
while (true) {
|
|
||||||
final long before = after;
|
|
||||||
final long currentProducerIndex = lvProducerIndex();
|
|
||||||
after = lvConsumerIndex();
|
|
||||||
if (before == after) {
|
|
||||||
//noinspection NumericCastThatLosesPrecision
|
|
||||||
return (int) (currentProducerIndex - after);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
boolean hasPendingMessages() {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final Object[] buffer = this.buffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
final Object previousElement = lpElement(buffer, calcElementOffset(consumerIndex, mask));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MultiNode.lpType(previousElement) != TYPE_CONSUMER || consumerIndex + this.consumerCount != producerIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
void busySpin(int spins) {
|
|
||||||
for (; ; ) {
|
|
||||||
if (spins > 0) {
|
|
||||||
--spins;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("null")
|
|
||||||
private
|
|
||||||
void park(final Object node, final Thread myThread) throws InterruptedException {
|
|
||||||
int spins = -1; // initialized after first item and cancel checks
|
|
||||||
Random randomYields = null; // bound if needed
|
|
||||||
|
|
||||||
for (; ; ) {
|
|
||||||
if (MultiNode.lvThread(node) == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (myThread.isInterrupted()) {
|
|
||||||
throw new InterruptedException();
|
|
||||||
}
|
|
||||||
else if (spins < 0) {
|
|
||||||
spins = PARK_UNTIMED_SPINS;
|
|
||||||
randomYields = randomThreadLocal.get();
|
|
||||||
}
|
|
||||||
else if (spins > 0) {
|
|
||||||
if (randomYields.nextInt(1024) == 0) {
|
|
||||||
UnsafeAccess.UNSAFE.park(false, 1L);
|
|
||||||
}
|
|
||||||
--spins;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// park can return for NO REASON (must check for thread values)
|
|
||||||
UnsafeAccess.UNSAFE.park(false, 0L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void unpark(Object node) {
|
|
||||||
final Object thread = MultiNode.lpThread(node);
|
|
||||||
MultiNode.soThread(node, null); // StoreStore
|
|
||||||
UnsafeAccess.UNSAFE.unpark(thread);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.publication.disruptor.MessageType;
|
|
||||||
import org.jctools.util.UnsafeAccess;
|
|
||||||
|
|
||||||
abstract class ColdItems {
|
|
||||||
private int type = 0;
|
|
||||||
|
|
||||||
private int messageType = MessageType.ONE;
|
|
||||||
private Object item1 = null;
|
|
||||||
private Object item2 = null;
|
|
||||||
private Object item3 = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Pad0 extends ColdItems {
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
volatile long y0, y1, y2, y4, y5, y6 = 7L;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class HotItem1 extends Pad0 {
|
|
||||||
private Thread thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
class MultiNode extends HotItem1 {
|
|
||||||
private static final long TYPE;
|
|
||||||
|
|
||||||
private static final long MESSAGETYPE;
|
|
||||||
private static final long ITEM1;
|
|
||||||
private static final long ITEM2;
|
|
||||||
private static final long ITEM3;
|
|
||||||
|
|
||||||
private static final long THREAD;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
TYPE = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("type"));
|
|
||||||
|
|
||||||
MESSAGETYPE = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("messageType"));
|
|
||||||
ITEM1 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item1"));
|
|
||||||
ITEM2 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item2"));
|
|
||||||
ITEM3 = UnsafeAccess.UNSAFE.objectFieldOffset(ColdItems.class.getDeclaredField("item3"));
|
|
||||||
THREAD = UnsafeAccess.UNSAFE.objectFieldOffset(HotItem1.class.getDeclaredField("thread"));
|
|
||||||
|
|
||||||
// now make sure we can access UNSAFE
|
|
||||||
MultiNode node = new MultiNode();
|
|
||||||
Object o = new Object();
|
|
||||||
spItem1(node, o);
|
|
||||||
Object lpItem1 = lpItem1(node);
|
|
||||||
spItem1(node, null);
|
|
||||||
|
|
||||||
if (lpItem1 != o) {
|
|
||||||
throw new Exception("Cannot access unsafe fields");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
void spMessageType(Object node, int messageType) {
|
|
||||||
UnsafeAccess.UNSAFE.putInt(node, MESSAGETYPE, messageType);
|
|
||||||
}
|
|
||||||
static
|
|
||||||
void spItem1(Object node, Object item) {
|
|
||||||
UnsafeAccess.UNSAFE.putObject(node, ITEM1, item);
|
|
||||||
}
|
|
||||||
static
|
|
||||||
void spItem2(Object node, Object item) {
|
|
||||||
UnsafeAccess.UNSAFE.putObject(node, ITEM2, item);
|
|
||||||
}
|
|
||||||
static
|
|
||||||
void spItem3(Object node, Object item) {
|
|
||||||
UnsafeAccess.UNSAFE.putObject(node, ITEM3, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// only used by the single transfer(item) method. Not used by the multi-transfer(*, *, *, *) method
|
|
||||||
static
|
|
||||||
void soItem1(Object node, Object item) {
|
|
||||||
UnsafeAccess.UNSAFE.putOrderedObject(node, ITEM1, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// only used by the single take() method. Not used by the void take(node)
|
|
||||||
static
|
|
||||||
Object lvItem1(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObjectVolatile(node, ITEM1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
Object lpMessageType(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObject(node, MESSAGETYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must call lvMessageType() BEFORE lpItem*() is called, because this ensures a LoadLoad for the data occurs.
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
int lvMessageType(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getIntVolatile(node, MESSAGETYPE);
|
|
||||||
}
|
|
||||||
public static
|
|
||||||
Object lpItem1(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObject(node, ITEM1);
|
|
||||||
}
|
|
||||||
public static
|
|
||||||
Object lpItem2(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObject(node, ITEM2);
|
|
||||||
}
|
|
||||||
public static
|
|
||||||
Object lpItem3(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObject(node, ITEM3);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////
|
|
||||||
static
|
|
||||||
void spType(Object node, int type) {
|
|
||||||
UnsafeAccess.UNSAFE.putInt(node, TYPE, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
int lpType(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getInt(node, TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////
|
|
||||||
static
|
|
||||||
void spThread(Object node, Object thread) {
|
|
||||||
UnsafeAccess.UNSAFE.putObject(node, THREAD, thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
void soThread(Object node, Object thread) {
|
|
||||||
UnsafeAccess.UNSAFE.putOrderedObject(node, THREAD, thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
Object lpThread(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObject(node, THREAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
Object lvThread(Object node) {
|
|
||||||
return UnsafeAccess.UNSAFE.getObjectVolatile(node, THREAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// post-padding
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
|
||||||
|
|
||||||
public MultiNode() {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -90,14 +90,6 @@ public interface PubSubSupport {
|
||||||
*/
|
*/
|
||||||
void publish(Object message1, Object message2, Object message3);
|
void publish(Object message1, Object message2, Object message3);
|
||||||
|
|
||||||
/**
|
|
||||||
* Synchronously publish <b>AN ARRAY</b> of messages to all registered listeners (that match the signature). This
|
|
||||||
* includes listeners defined for super types of the given message type, provided they are not configured
|
|
||||||
* to reject valid subtypes. The call returns when all matching handlers of all registered listeners have
|
|
||||||
* been notified (invoked) of the message.
|
|
||||||
*/
|
|
||||||
void publish(Object[] message);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish the message asynchronously to all registered listeners (that match the signature). This includes
|
* Publish the message asynchronously to all registered listeners (that match the signature). This includes
|
||||||
* listeners defined for super types of the given message type, provided they are not configured to reject
|
* listeners defined for super types of the given message type, provided they are not configured to reject
|
||||||
|
@ -136,17 +128,4 @@ public interface PubSubSupport {
|
||||||
* return.
|
* return.
|
||||||
*/
|
*/
|
||||||
void publishAsync(Object message1, Object message2, Object message3);
|
void publishAsync(Object message1, Object message2, Object message3);
|
||||||
|
|
||||||
/**
|
|
||||||
* Publish <b>AN ARRAY</b> of messages asynchronously to all registered listeners (that match the signature). This
|
|
||||||
* includes listeners defined for super types of the given message type, provided they are not configured to
|
|
||||||
* reject valid subtypes. The call returns when all matching handlers of all registered listeners have been
|
|
||||||
* notified (invoked) of the message.
|
|
||||||
* <p>
|
|
||||||
* <p>
|
|
||||||
* The behavior of this method depends on availability of workers. If all workers are busy, then this method
|
|
||||||
* will block until there is an available worker. If workers are available, then this method will immediately
|
|
||||||
* return.
|
|
||||||
*/
|
|
||||||
void publishAsync(Object[] messages);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,14 +54,6 @@ import java.lang.annotation.*;
|
||||||
public
|
public
|
||||||
@interface Handler {
|
@interface Handler {
|
||||||
|
|
||||||
/**
|
|
||||||
* Define whether or not the handler accepts variable arguments it declares in its signature.
|
|
||||||
* VarArg "acceptance" means that the handler, handle(String... s), will accept a publication
|
|
||||||
* of ("s"), ("s", "s"), or (String[3]{"s", "s", "s"}). By default, handle(String... s) will
|
|
||||||
* only handle publications that are exactly an array (String[3]{"s"})
|
|
||||||
*/
|
|
||||||
boolean acceptVarargs() default false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define whether or not the handler accepts sub types of the message type it declares in its
|
* Define whether or not the handler accepts sub types of the message type it declares in its
|
||||||
* signature.
|
* signature.
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package dorkbox.util.messagebus.annotations;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This annotation is meant to carry configuration that is shared among all instances of the annotated
|
|
||||||
* listener. Supported configurations are:
|
|
||||||
* <p/>
|
|
||||||
* Reference type: The bus will use either strong or weak references to its registered listeners,
|
|
||||||
* depending on which reference type (@see References) is set
|
|
||||||
*
|
|
||||||
* @author bennidi
|
|
||||||
*/
|
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
|
||||||
@Target(value = {ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
|
||||||
@Inherited
|
|
||||||
public
|
|
||||||
@interface Listener {}
|
|
|
@ -1,252 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This data structure is optimized for non-blocking reads even when write operations occur.
|
|
||||||
* Running read iterators will not be affected by add operations since writes always insert at the head of the
|
|
||||||
* structure. Remove operations can affect any running iterator such that a removed element that has not yet
|
|
||||||
* been reached by the iterator will not appear in that iterator anymore.
|
|
||||||
*
|
|
||||||
* @author bennidi
|
|
||||||
* Date: 2/12/12
|
|
||||||
*/
|
|
||||||
public abstract
|
|
||||||
class AbstractConcurrentSet<T> implements Set<T> {
|
|
||||||
private static final AtomicLong id = new AtomicLong();
|
|
||||||
private final transient long ID = id.getAndIncrement();
|
|
||||||
|
|
||||||
// Internal state
|
|
||||||
protected final StampedLock lock = new StampedLock();
|
|
||||||
private final Map<T, ISetEntry<T>> entries; // maintain a map of entries for O(log n) lookup
|
|
||||||
|
|
||||||
public volatile Entry<T> head; // reference to the first element
|
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
|
||||||
|
|
||||||
|
|
||||||
protected
|
|
||||||
AbstractConcurrentSet(Map<T, ISetEntry<T>> entries) {
|
|
||||||
this.entries = entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract
|
|
||||||
Entry<T> createEntry(T value, Entry<T> next);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean add(T element) {
|
|
||||||
if (element == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean changed;
|
|
||||||
|
|
||||||
final StampedLock lock = this.lock;
|
|
||||||
long stamp = lock.readLock();
|
|
||||||
if (this.entries.containsKey(element)) {
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long origStamp = stamp;
|
|
||||||
if ((stamp = lock.tryConvertToWriteLock(stamp)) == 0) {
|
|
||||||
lock.unlockRead(origStamp);
|
|
||||||
stamp = lock.writeLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
changed = insert(element);
|
|
||||||
lock.unlock(stamp);
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean contains(Object element) {
|
|
||||||
final StampedLock lock = this.lock;
|
|
||||||
long stamp = lock.readLock();
|
|
||||||
|
|
||||||
ISetEntry<T> entry = this.entries.get(element);
|
|
||||||
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
return entry != null && entry.getValue() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
boolean insert(T element) {
|
|
||||||
if (!this.entries.containsKey(element)) {
|
|
||||||
this.head = createEntry(element, this.head);
|
|
||||||
this.entries.put(element, this.head);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
int size() {
|
|
||||||
return this.entries.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean isEmpty() {
|
|
||||||
return this.head == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean addAll(Collection<? extends T> elements) {
|
|
||||||
StampedLock lock = this.lock;
|
|
||||||
|
|
||||||
boolean changed = false;
|
|
||||||
long stamp = lock.writeLock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (T element : elements) {
|
|
||||||
if (element != null) {
|
|
||||||
changed |= insert(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return TRUE if the element was successfully removed
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean remove(Object element) {
|
|
||||||
StampedLock lock = this.lock;
|
|
||||||
long stamp = lock.readLock();
|
|
||||||
|
|
||||||
ISetEntry<T> entry = this.entries.get(element);
|
|
||||||
|
|
||||||
lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (entry == null || entry.getValue() == null) {
|
|
||||||
return false; // fast exit
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
stamp = lock.writeLock();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (entry != this.head) {
|
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// if it was second, now it's first
|
|
||||||
this.head = this.head.next();
|
|
||||||
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
|
||||||
}
|
|
||||||
this.entries.remove(element);
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object[] toArray() {
|
|
||||||
return this.entries.entrySet().toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
<T2> T2[] toArray(T2[] a) {
|
|
||||||
return this.entries.entrySet().toArray(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean containsAll(Collection<?> c) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean removeAll(Collection<?> c) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean retainAll(Collection<?> c) {
|
|
||||||
throw new UnsupportedOperationException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void clear() {
|
|
||||||
StampedLock lock = this.lock;
|
|
||||||
|
|
||||||
long stamp = lock.writeLock();
|
|
||||||
this.head = null;
|
|
||||||
this.entries.clear();
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
int hashCode() {
|
|
||||||
final int prime = 31;
|
|
||||||
int result = 1;
|
|
||||||
result = prime * result + (int) (this.ID ^ this.ID >>> 32);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
AbstractConcurrentSet other = (AbstractConcurrentSet) obj;
|
|
||||||
if (this.ID != other.ID) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
215
src/dorkbox/util/messagebus/common/ClassTree.java
Normal file
215
src/dorkbox/util/messagebus/common/ClassTree.java
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple tree structure that is a map that contains a chain of keys to publish to a value.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This Tree store "message classes" as the key, and a unique object as the "value". This map is NEVER cleared (shutdown clears it), and
|
||||||
|
* the "value" object is used to store/lookup in another map
|
||||||
|
*
|
||||||
|
* This data structure is used to keep track of multi-messages - where there is more that one parameter for publish().
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public class ClassTree<KEY> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static int INITIAL_SIZE = 4;
|
||||||
|
public static float LOAD_FACTOR = 0.8F;
|
||||||
|
|
||||||
|
private static
|
||||||
|
final ThreadLocal<IdentityMap> keyCache = new ThreadLocal<IdentityMap>() {
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
IdentityMap initialValue() {
|
||||||
|
return new IdentityMap(INITIAL_SIZE, LOAD_FACTOR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static
|
||||||
|
final ThreadLocal<MultiClass> valueCache = new ThreadLocal<MultiClass>();
|
||||||
|
|
||||||
|
private AtomicReference<Object> children = new AtomicReference<Object>();
|
||||||
|
private AtomicReference<MultiClass> value = new AtomicReference<MultiClass>();
|
||||||
|
private AtomicInteger valueId = new AtomicInteger(Integer.MIN_VALUE);
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <KEY> IdentityMap<KEY, ClassTree<KEY>> cast(Object o) {
|
||||||
|
return (IdentityMap<KEY, ClassTree<KEY>>) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public
|
||||||
|
ClassTree() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public final void clear() {
|
||||||
|
// gc handles the rest
|
||||||
|
this.children.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public final
|
||||||
|
MultiClass get(KEY key) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassTree<KEY> leaf = getOrCreateLeaf(key);
|
||||||
|
return getOrCreateValue(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final
|
||||||
|
MultiClass get(KEY key1, KEY key2) {
|
||||||
|
if (key1 == null || key2 == null) {
|
||||||
|
throw new NullPointerException("keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to put value into our children
|
||||||
|
ClassTree<KEY> leaf = getOrCreateLeaf(key1);
|
||||||
|
leaf = leaf.getOrCreateLeaf(key2);
|
||||||
|
|
||||||
|
return getOrCreateValue(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final
|
||||||
|
MultiClass get(KEY key1, KEY key2, KEY key3) {
|
||||||
|
if (key1 == null || key2 == null || key3 == null) {
|
||||||
|
throw new NullPointerException("keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to put value into our children
|
||||||
|
ClassTree<KEY> leaf = getOrCreateLeaf(key1);
|
||||||
|
leaf = leaf.getOrCreateLeaf(key2);
|
||||||
|
leaf = leaf.getOrCreateLeaf(key3);
|
||||||
|
|
||||||
|
return getOrCreateValue(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final
|
||||||
|
MultiClass get(KEY... keys) {
|
||||||
|
if (keys == null) {
|
||||||
|
throw new NullPointerException("keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = keys.length;
|
||||||
|
|
||||||
|
// have to put value into our children
|
||||||
|
ClassTree<KEY> leaf = getOrCreateLeaf(keys[0]);
|
||||||
|
for (int i=1;i<length;i++) {
|
||||||
|
leaf = leaf.getOrCreateLeaf(keys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOrCreateValue(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a child (if necessary) in an atomic way. The tree returned will either be the current one, or a new one.
|
||||||
|
*
|
||||||
|
* @param key the key for the new child
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private
|
||||||
|
ClassTree<KEY> getOrCreateLeaf(KEY key) {
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the children, then insert a tree @ KEY location inside children
|
||||||
|
|
||||||
|
final Object cached = keyCache.get();
|
||||||
|
final Object checked = children.get();
|
||||||
|
IdentityMap<KEY, ClassTree<KEY>> kids;
|
||||||
|
|
||||||
|
// create the children, if necessary
|
||||||
|
if (checked == null) {
|
||||||
|
final boolean success = children.compareAndSet(null, cached);
|
||||||
|
if (success) {
|
||||||
|
keyCache.set(new IdentityMap(INITIAL_SIZE, LOAD_FACTOR));
|
||||||
|
kids = cast(cached);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
kids = cast(children.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
kids = cast(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new tree inside the children, if necessary
|
||||||
|
ClassTree<KEY> targetTree = kids.get(key);
|
||||||
|
if (targetTree == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
// only one thread can insert into the kids. DCL is safe because we safely publish at the end
|
||||||
|
targetTree = kids.get(key);
|
||||||
|
|
||||||
|
if (targetTree == null) {
|
||||||
|
targetTree = new ClassTree<KEY>();
|
||||||
|
kids.put(key, targetTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean success = children.compareAndSet(kids, kids); // make sure our kids are what we expect and ALSO publishes them
|
||||||
|
if (!success) {
|
||||||
|
throw new RuntimeException("Error setting children in leaf!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets, or creates, in an atomic way, the value for a MapTree
|
||||||
|
*
|
||||||
|
* @param leaf where to get/create the value
|
||||||
|
* @return non-null
|
||||||
|
*/
|
||||||
|
private
|
||||||
|
MultiClass getOrCreateValue(final ClassTree<KEY> leaf) {
|
||||||
|
MultiClass value = leaf.value.get();
|
||||||
|
if (value == null) {
|
||||||
|
MultiClass multiClass = valueCache.get();
|
||||||
|
if (multiClass == null) {
|
||||||
|
multiClass = new MultiClass(valueId.getAndIncrement());
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean success = leaf.value.compareAndSet(null, multiClass);
|
||||||
|
if (success) {
|
||||||
|
valueCache.set(new MultiClass(valueId.getAndIncrement()));
|
||||||
|
return multiClass;
|
||||||
|
} else {
|
||||||
|
valueCache.set(multiClass);
|
||||||
|
return leaf.value.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,38 +15,21 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
abstract class pad0<T> {
|
// not thread-safe! must be synchronized in enclosing context
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
public class Entry {
|
||||||
}
|
private Object value;
|
||||||
|
private Entry next;
|
||||||
|
private Entry prev;
|
||||||
|
|
||||||
abstract class item1<T> extends pad0<T>{
|
public Entry(Object value, Entry next) {
|
||||||
Entry<T> next;
|
if (next != null) {
|
||||||
}
|
this.next = next;
|
||||||
|
next.prev = this;
|
||||||
|
}
|
||||||
|
|
||||||
abstract class pad1<T> extends item1<T> {
|
this.value = value;
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class item2<T> extends pad1<T> {
|
|
||||||
Entry<T> prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class pad2<T> extends item2<T> {
|
|
||||||
volatile long z0, z1, z2, z4, z5, z6 = 7L;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class Entry<T> extends pad2<T> implements ISetEntry<T> {
|
|
||||||
protected Entry(Entry<T> next) {
|
|
||||||
this.next = next;
|
|
||||||
next.prev = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Entry() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// not thread-safe! must be synchronized in enclosing context
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
if (this.prev != null) {
|
if (this.prev != null) {
|
||||||
this.prev.next = this.next;
|
this.prev.next = this.next;
|
||||||
|
@ -62,13 +45,16 @@ public abstract class Entry<T> extends pad2<T> implements ISetEntry<T> {
|
||||||
//predecessor = null;
|
//predecessor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Entry next() {
|
||||||
public Entry<T> next() {
|
|
||||||
return this.next;
|
return this.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.next = null;
|
this.next = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,504 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple tree structure that is a map that contains a chain of keys to publish to a value.
|
|
||||||
*
|
|
||||||
* @author dorkbox, llc
|
|
||||||
* Date: 2/2/15
|
|
||||||
*/
|
|
||||||
public class HashMapTree<KEY, VALUE> {
|
|
||||||
public static int INITIAL_SIZE = 4;
|
|
||||||
public static float LOAD_FACTOR = 0.8F;
|
|
||||||
|
|
||||||
|
|
||||||
private static
|
|
||||||
final ThreadLocal<Object> keyCache = new ThreadLocal<Object>() {
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Object initialValue() {
|
|
||||||
return new ConcurrentHashMap(INITIAL_SIZE, LOAD_FACTOR, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static
|
|
||||||
final ThreadLocal<Object> valueCache = new ThreadLocal<Object>() {
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Object initialValue() {
|
|
||||||
return new HashMapTree();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map<KEY, HashMapTree<KEY, VALUE>>
|
|
||||||
private AtomicReference<Object> children = new AtomicReference<Object>();
|
|
||||||
private VALUE value;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <KEY, VALUE> ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> cast(Object o) {
|
|
||||||
return (ConcurrentMap<KEY, HashMapTree<KEY, VALUE>>) o;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public HashMapTree() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final VALUE getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final void putValue(VALUE value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final void removeValue() {
|
|
||||||
this.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final void clear() {
|
|
||||||
// if (this.children != null) {
|
|
||||||
// Set<Entry<KEY, HashMapTree<KEY, VALUE>>> entrySet = this.children.entrySet();
|
|
||||||
// for (Entry<KEY, HashMapTree<KEY, VALUE>> entry : entrySet) {
|
|
||||||
// entry.getValue().clear();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// this.children.clear();
|
|
||||||
// this.value = null;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final VALUE put(KEY key, VALUE value) {
|
|
||||||
if (key == null) {
|
|
||||||
throw new NullPointerException("keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
// have to put value into our children
|
|
||||||
HashMapTree<KEY, VALUE> leaf = createLeaf(key);
|
|
||||||
VALUE prev = leaf.value;
|
|
||||||
leaf.value = value;
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final VALUE put(VALUE value, KEY key1, KEY key2) {
|
|
||||||
if (key1 == null || key2 == null) {
|
|
||||||
throw new NullPointerException("keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
// have to put value into our children
|
|
||||||
HashMapTree<KEY, VALUE> leaf = createLeaf(key1);
|
|
||||||
leaf = leaf.createLeaf(key2);
|
|
||||||
|
|
||||||
VALUE prev = leaf.value;
|
|
||||||
leaf.value = value;
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final VALUE put(VALUE value, KEY key1, KEY key2, KEY key3) {
|
|
||||||
if (key1 == null || key2 == null || key3 == null) {
|
|
||||||
throw new NullPointerException("keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
// have to put value into our children
|
|
||||||
HashMapTree<KEY, VALUE> leaf = createLeaf(key1);
|
|
||||||
leaf = leaf.createLeaf(key2);
|
|
||||||
leaf = leaf.createLeaf(key3);
|
|
||||||
|
|
||||||
VALUE prev = leaf.value;
|
|
||||||
leaf.value = value;
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final VALUE put(VALUE value, KEY... keys) {
|
|
||||||
if (keys == null) {
|
|
||||||
throw new NullPointerException("keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
int length = keys.length;
|
|
||||||
|
|
||||||
// have to put value into our children
|
|
||||||
HashMapTree<KEY, VALUE> leaf = createLeaf(keys[0]);
|
|
||||||
for (int i=1;i<length;i++) {
|
|
||||||
leaf = leaf.createLeaf(keys[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
VALUE prev = leaf.value;
|
|
||||||
leaf.value = value;
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final HashMapTree<KEY, VALUE> createLeaf(KEY... keys) {
|
|
||||||
if (keys == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
int length = keys.length;
|
|
||||||
|
|
||||||
// have to put value into our children
|
|
||||||
HashMapTree<KEY, VALUE> leaf = createLeaf(keys[0]);
|
|
||||||
for (int i=1;i<length;i++) {
|
|
||||||
leaf = leaf.createLeaf(keys[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a child (if necessary) in an atomic way. The tree returned will either be the current one, or a new one.
|
|
||||||
*
|
|
||||||
* @param key the key for the new child
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private
|
|
||||||
HashMapTree<KEY, VALUE> createLeaf(KEY key) {
|
|
||||||
if (key == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Object cached = keyCache.get();
|
|
||||||
final Object checked = children.get();
|
|
||||||
ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids;
|
|
||||||
|
|
||||||
if (checked == null) {
|
|
||||||
final boolean success = children.compareAndSet(null, cached);
|
|
||||||
if (success) {
|
|
||||||
keyCache.set(new ConcurrentHashMap(INITIAL_SIZE, LOAD_FACTOR, 1));
|
|
||||||
kids = cast(cached);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
kids = cast(children.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
kids = cast(checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final Object cached2 = valueCache.get();
|
|
||||||
final HashMapTree<KEY, VALUE> tree = kids.putIfAbsent(key, (HashMapTree<KEY, VALUE>) cached2);
|
|
||||||
if (tree == null) {
|
|
||||||
// was absent
|
|
||||||
valueCache.set(new HashMapTree());
|
|
||||||
return (HashMapTree<KEY, VALUE>) cached2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
public final VALUE get(KEY key) {
|
|
||||||
if (key == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> objectTree;
|
|
||||||
// publish value from our children
|
|
||||||
objectTree = getLeaf(key);
|
|
||||||
|
|
||||||
if (objectTree == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return objectTree.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final VALUE get(KEY key1, KEY key2) {
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(key1); // protected by lock
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final VALUE get(KEY key1, KEY key2, KEY key3) {
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(key1);
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key2);
|
|
||||||
}
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public final VALUE get(KEY... keys) {
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(keys[0]);
|
|
||||||
|
|
||||||
int size = keys.length;
|
|
||||||
for (int i=1;i<size;i++) {
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(keys[i]);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tree == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final HashMapTree<KEY, VALUE> getLeaf(KEY key) {
|
|
||||||
if (key == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
|
|
||||||
if (this.children == null) {
|
|
||||||
tree = null;
|
|
||||||
} else {
|
|
||||||
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> o = cast(this.children.get());
|
|
||||||
tree = o.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final HashMapTree<KEY, VALUE> getLeaf(KEY key1, KEY key2) {
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(key1);
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final HashMapTree<KEY, VALUE> getLeaf(KEY key1, KEY key2, KEY key3) {
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(key1);
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key2);
|
|
||||||
}
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(key3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public final HashMapTree<KEY, VALUE> getLeaf(KEY... keys) {
|
|
||||||
int size = keys.length;
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> tree;
|
|
||||||
// publish value from our children
|
|
||||||
tree = getLeaf(keys[0]);
|
|
||||||
|
|
||||||
for (int i=1;i<size;i++) {
|
|
||||||
if (tree != null) {
|
|
||||||
tree = tree.getLeaf(keys[i]);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This <b>IS NOT</b> safe to call outside of root.remove(...)
|
|
||||||
* <p>
|
|
||||||
* Removes a branch from the tree, and cleans up, if necessary
|
|
||||||
*/
|
|
||||||
public final void remove(KEY key) {
|
|
||||||
if (key != null) {
|
|
||||||
removeLeaf(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This <b>IS NOT</b> safe to call outside of root.remove(...)
|
|
||||||
* <p>
|
|
||||||
* Removes a branch from the tree, and cleans up, if necessary
|
|
||||||
*/
|
|
||||||
public final void remove(KEY key1, KEY key2) {
|
|
||||||
if (key1 == null || key2 == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMapTree<KEY, VALUE> leaf;
|
|
||||||
if (this.children != null) {
|
|
||||||
leaf = getLeaf(key1);
|
|
||||||
|
|
||||||
if (leaf != null) {
|
|
||||||
leaf.removeLeaf(key2);
|
|
||||||
|
|
||||||
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> o = cast(this.children.get());
|
|
||||||
o.remove(key1);
|
|
||||||
|
|
||||||
if (o.isEmpty()) {
|
|
||||||
this.children.compareAndSet(o, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This <b>IS NOT</b> safe to call outside of root.remove(...)
|
|
||||||
* <p>
|
|
||||||
* Removes a branch from the tree, and cleans up, if necessary
|
|
||||||
*/
|
|
||||||
public final void remove(KEY key1, KEY key2, KEY key3) {
|
|
||||||
if (key1 == null || key2 == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashMapTree<KEY, VALUE> leaf;
|
|
||||||
// if (this.children != null) {
|
|
||||||
// leaf = getLeaf(key1);
|
|
||||||
//
|
|
||||||
// if (leaf != null) {
|
|
||||||
// leaf.remove(key2, key3);
|
|
||||||
// this.children.remove(key1);
|
|
||||||
//
|
|
||||||
// if (this.children.isEmpty()) {
|
|
||||||
// this.children = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This <b>IS NOT</b> safe to call outside of root.remove(...)
|
|
||||||
* <p>
|
|
||||||
* Removes a branch from the tree, and cleans up, if necessary
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public final void remove(KEY... keys) {
|
|
||||||
if (keys == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeLeaf(0, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a branch from the tree, and cleans up, if necessary
|
|
||||||
*/
|
|
||||||
private void removeLeaf(KEY key) {
|
|
||||||
if (key != null) {
|
|
||||||
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids = cast(this.children.get());
|
|
||||||
|
|
||||||
if (kids != null) {
|
|
||||||
kids.remove(key);
|
|
||||||
|
|
||||||
if (kids.isEmpty()) {
|
|
||||||
this.children.compareAndSet(kids, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keys CANNOT be null here!
|
|
||||||
private void removeLeaf(int index, KEY[] keys) {
|
|
||||||
if (index == keys.length) {
|
|
||||||
// we have reached the leaf to remove!
|
|
||||||
this.value = null;
|
|
||||||
this.children = null;
|
|
||||||
} else {
|
|
||||||
final ConcurrentMap<KEY, HashMapTree<KEY, VALUE>> kids = cast(this.children.get());
|
|
||||||
if (kids != null) {
|
|
||||||
// HashMapTree<KEY, VALUE> leaf = this.children.get(keys[index]);
|
|
||||||
|
|
||||||
// if (leaf != null) {
|
|
||||||
// leaf.removeLeaf(index + 1, keys);
|
|
||||||
// if (leaf.children == null && leaf.value == null) {
|
|
||||||
// this.children.remove(keys[index]);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (this.children.isEmpty()) {
|
|
||||||
// this.children = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Todo: Add javadoc
|
|
||||||
*
|
|
||||||
* @author bennidi
|
|
||||||
* Date: 3/29/13
|
|
||||||
*/
|
|
||||||
public interface ISetEntry<T> {
|
|
||||||
|
|
||||||
T getValue();
|
|
||||||
|
|
||||||
// not thread-safe! must be synchronized in enclosing context
|
|
||||||
void remove();
|
|
||||||
|
|
||||||
ISetEntry<T> next();
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
}
|
|
|
@ -37,14 +37,12 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.common;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
|
||||||
import dorkbox.util.messagebus.annotations.Handler;
|
import dorkbox.util.messagebus.annotations.Handler;
|
||||||
import dorkbox.util.messagebus.annotations.Synchronized;
|
import dorkbox.util.messagebus.annotations.Synchronized;
|
||||||
import dorkbox.util.messagebus.utils.ReflectionUtils;
|
import dorkbox.util.messagebus.utils.ReflectionUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
|
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
|
||||||
|
@ -104,38 +102,24 @@ class MessageHandler {
|
||||||
return finalMethods.toArray(new MessageHandler[0]);
|
return finalMethods.toArray(new MessageHandler[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final MethodAccess handler;
|
private final Method method;
|
||||||
private final int methodIndex;
|
|
||||||
|
|
||||||
private final Class<?>[] handledMessages;
|
private final Class<?>[] handledMessages;
|
||||||
private final boolean acceptsSubtypes;
|
private final boolean acceptsSubtypes;
|
||||||
private final Class<?> varArgClass;
|
|
||||||
|
|
||||||
private final boolean isSynchronized;
|
private final boolean isSynchronized;
|
||||||
|
|
||||||
public
|
private
|
||||||
MessageHandler(Method handler, Handler handlerConfig) {
|
MessageHandler(final Method method, final Handler config) {
|
||||||
super();
|
if (method == null) {
|
||||||
|
throw new IllegalArgumentException("The message method configuration may not be null");
|
||||||
if (handler == null) {
|
|
||||||
throw new IllegalArgumentException("The message handler configuration may not be null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?>[] handledMessages = handler.getParameterTypes();
|
this.method = method;
|
||||||
|
this.acceptsSubtypes = config.acceptSubtypes();
|
||||||
this.handler = MethodAccess.get(handler.getDeclaringClass());
|
this.handledMessages = method.getParameterTypes();
|
||||||
this.methodIndex = this.handler.getIndex(handler.getName(), handledMessages);
|
this.isSynchronized = ReflectionUtils.getAnnotation(method, Synchronized.class) != null;
|
||||||
|
|
||||||
this.acceptsSubtypes = handlerConfig.acceptSubtypes();
|
|
||||||
this.isSynchronized = ReflectionUtils.getAnnotation(handler, Synchronized.class) != null;
|
|
||||||
this.handledMessages = handledMessages;
|
|
||||||
|
|
||||||
if (handledMessages.length == 1 && handledMessages[0].isArray() && handlerConfig.acceptVarargs()) {
|
|
||||||
this.varArgClass = handledMessages[0].getComponentType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.varArgClass = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
public final
|
||||||
|
@ -143,14 +127,11 @@ class MessageHandler {
|
||||||
return this.isSynchronized;
|
return this.isSynchronized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
|
||||||
MethodAccess getHandler() {
|
|
||||||
return this.handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final
|
public final
|
||||||
int getMethodIndex() {
|
Method getMethod() {
|
||||||
return this.methodIndex;
|
return this.method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
public final
|
||||||
|
@ -158,31 +139,15 @@ class MessageHandler {
|
||||||
return this.handledMessages;
|
return this.handledMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
|
||||||
Class<?> getVarArgClass() {
|
|
||||||
return this.varArgClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final
|
public final
|
||||||
boolean acceptsSubtypes() {
|
boolean acceptsSubtypes() {
|
||||||
return this.acceptsSubtypes;
|
return this.acceptsSubtypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
|
||||||
boolean acceptsVarArgs() {
|
|
||||||
return this.varArgClass != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
int hashCode() {
|
int hashCode() {
|
||||||
final int prime = 31;
|
return this.method.hashCode();
|
||||||
int result = 1;
|
|
||||||
result = prime * result + (this.acceptsSubtypes ? 1231 : 1237);
|
|
||||||
result = prime * result + Arrays.hashCode(this.handledMessages);
|
|
||||||
result = prime * result + (this.handler == null ? 0 : this.handler.hashCode());
|
|
||||||
result = prime * result + (this.isSynchronized ? 1231 : 1237);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -197,24 +162,8 @@ class MessageHandler {
|
||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageHandler other = (MessageHandler) obj;
|
MessageHandler other = (MessageHandler) obj;
|
||||||
if (this.acceptsSubtypes != other.acceptsSubtypes) {
|
return this.method.equals(other.method);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Arrays.equals(this.handledMessages, other.handledMessages)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.handler == null) {
|
|
||||||
if (other.handler != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!this.handler.equals(other.handler)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.isSynchronized != other.isSynchronized) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
50
src/dorkbox/util/messagebus/common/MultiClass.java
Normal file
50
src/dorkbox/util/messagebus/common/MultiClass.java
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class MultiClass implements Comparable<MultiClass> {
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
public
|
||||||
|
MultiClass(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int compareTo(final MultiClass o) {
|
||||||
|
if (value < o.value) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (value == o.value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MultiClass that = (MultiClass) o;
|
||||||
|
|
||||||
|
return value == that.value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
int hashCode() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.common.thread;
|
package dorkbox.util.messagebus.common;
|
||||||
|
|
||||||
import java.lang.management.RuntimeMXBean;
|
import java.lang.management.RuntimeMXBean;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -68,7 +68,7 @@ class NamedThreadFactory implements ThreadFactory {
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
value = Integer.parseInt(stackSize.substring(4));
|
value = Integer.parseInt(stackSize.substring(4));
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This implementation uses strong references to the elements.
|
|
||||||
* <p/>
|
|
||||||
*
|
|
||||||
* @author bennidi
|
|
||||||
* Date: 2/12/12
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class StrongConcurrentSet<T> extends AbstractConcurrentSet<T> {
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
StrongConcurrentSet() {
|
|
||||||
this(16, 0.75f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
StrongConcurrentSet(int size, float loadFactor) {
|
|
||||||
this(new HashMap<T, ISetEntry<T>>(size, loadFactor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
StrongConcurrentSet(Map<T, ISetEntry<T>> entries) {
|
|
||||||
super(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Iterator<T> iterator() {
|
|
||||||
return new Iterator<T>() {
|
|
||||||
private ISetEntry<T> current = StrongConcurrentSet.this.head;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasNext() {
|
|
||||||
return this.current != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
T next() {
|
|
||||||
if (this.current == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
T value = this.current.getValue();
|
|
||||||
this.current = this.current.next();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void remove() {
|
|
||||||
if (this.current == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ISetEntry<T> newCurrent = this.current.next();
|
|
||||||
StrongConcurrentSet.this.remove(this.current.getValue());
|
|
||||||
this.current = newCurrent;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Entry<T> createEntry(T value, Entry<T> next) {
|
|
||||||
return next != null ? new StrongEntry<T>(value, next) : new StrongEntry<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static
|
|
||||||
class StrongEntry<T> extends Entry<T> {
|
|
||||||
|
|
||||||
private T value;
|
|
||||||
|
|
||||||
private
|
|
||||||
StrongEntry(T value, Entry<T> next) {
|
|
||||||
super(next);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
StrongEntry(T value) {
|
|
||||||
super();
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
T getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This implementation uses strong references to the elements, uses an IdentityHashMap
|
|
||||||
* <p/>
|
|
||||||
*
|
|
||||||
* @author dorkbox
|
|
||||||
* Date: 2/2/15
|
|
||||||
*/
|
|
||||||
public class StrongConcurrentSetV8<T> extends StrongConcurrentSet<T> {
|
|
||||||
|
|
||||||
public StrongConcurrentSetV8(int size, float loadFactor) {
|
|
||||||
// 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks)
|
|
||||||
super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
public StrongConcurrentSetV8(int size, float loadFactor, int stripeSize) {
|
|
||||||
// 1 for the stripe size, because that is the max concurrency with our concurrent set (since it uses R/W locks)
|
|
||||||
super(new ConcurrentHashMap<T, ISetEntry<T>>(size, loadFactor, stripeSize));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package dorkbox.util.messagebus.common;
|
|
||||||
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
import java.util.concurrent.locks.StampedLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This implementation uses weak references to the elements. Iterators automatically perform cleanups of
|
|
||||||
* garbage collected objects during iteration -> no dedicated maintenance operations need to be called or run in background.
|
|
||||||
*
|
|
||||||
* @author bennidi
|
|
||||||
* Date: 2/12/12
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class WeakConcurrentSet<T> extends AbstractConcurrentSet<T> {
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
WeakConcurrentSet() {
|
|
||||||
super(new WeakHashMap<T, ISetEntry<T>>());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Iterator<T> iterator() {
|
|
||||||
return new Iterator<T>() {
|
|
||||||
|
|
||||||
// the current list element of this iterator
|
|
||||||
// used to keep track of the iteration process
|
|
||||||
private ISetEntry<T> current = WeakConcurrentSet.this.head;
|
|
||||||
|
|
||||||
// this method will remove all orphaned entries
|
|
||||||
// until it finds the first entry whose value has not yet been garbage collected
|
|
||||||
// the method assumes that the current element is already orphaned and will remove it
|
|
||||||
private
|
|
||||||
void removeOrphans() {
|
|
||||||
|
|
||||||
StampedLock lock = WeakConcurrentSet.this.lock;
|
|
||||||
long stamp = lock.writeLock();
|
|
||||||
// final Lock writeLock = WeakConcurrentSet.this.lock.writeLock();
|
|
||||||
// writeLock.lock();
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
ISetEntry<T> orphaned = this.current;
|
|
||||||
this.current = this.current.next();
|
|
||||||
orphaned.remove();
|
|
||||||
} while (this.current != null && this.current.getValue() == null);
|
|
||||||
} finally {
|
|
||||||
lock.unlockWrite(stamp);
|
|
||||||
// writeLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasNext() {
|
|
||||||
if (this.current == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.current.getValue() == null) {
|
|
||||||
// trigger removal of orphan references
|
|
||||||
// because a null value indicates that the value has been garbage collected
|
|
||||||
removeOrphans();
|
|
||||||
return this.current != null; // if any entry is left then it will have a value
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
T next() {
|
|
||||||
if (this.current == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
T value = this.current.getValue();
|
|
||||||
if (value == null) { // auto-removal of orphan references
|
|
||||||
removeOrphans();
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.current = this.current.next();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void remove() {
|
|
||||||
//throw new UnsupportedOperationException("Explicit removal of set elements is only allowed via the controlling set. Sorry!");
|
|
||||||
if (this.current == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ISetEntry<T> newCurrent = this.current.next();
|
|
||||||
WeakConcurrentSet.this.remove(this.current.getValue());
|
|
||||||
this.current = newCurrent;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Entry<T> createEntry(T value, Entry<T> next) {
|
|
||||||
return next != null ? new WeakEntry<T>(value, next) : new WeakEntry<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static
|
|
||||||
class WeakEntry<T> extends Entry<T> {
|
|
||||||
|
|
||||||
private WeakReference<T> value;
|
|
||||||
|
|
||||||
private
|
|
||||||
WeakEntry(T value, Entry<T> next) {
|
|
||||||
super(next);
|
|
||||||
this.value = new WeakReference<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
WeakEntry(T value) {
|
|
||||||
super();
|
|
||||||
this.value = new WeakReference<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
T getValue() {
|
|
||||||
return this.value.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,943 +0,0 @@
|
||||||
/*
|
|
||||||
* Written by Doug Lea and Martin Buchholz with assistance from members of
|
|
||||||
* JCP JSR-166 Expert Group and released to the public domain, as explained
|
|
||||||
* at http://creativecommons.org/publicdomain/zero/1.0/
|
|
||||||
*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.common.thread;
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
|
||||||
|
|
||||||
import java.util.AbstractQueue;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Spliterator;
|
|
||||||
import java.util.Spliterators;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An unbounded thread-safe {@linkplain Queue queue} based on linked nodes.
|
|
||||||
* This queue orders elements FIFO (first-in-first-out).
|
|
||||||
* The <em>head</em> of the queue is that element that has been on the
|
|
||||||
* queue the longest time.
|
|
||||||
* The <em>tail</em> of the queue is that element that has been on the
|
|
||||||
* queue the shortest time. New elements
|
|
||||||
* are inserted at the tail of the queue, and the queue retrieval
|
|
||||||
* operations obtain elements at the head of the queue.
|
|
||||||
* A {@code ConcurrentLinkedQueue} is an appropriate choice when
|
|
||||||
* many threads will share access to a common collection.
|
|
||||||
* Like most other concurrent collection implementations, this class
|
|
||||||
* does not permit the use of {@code null} elements.
|
|
||||||
*
|
|
||||||
* <p>This implementation employs an efficient <em>non-blocking</em>
|
|
||||||
* algorithm based on one described in <a
|
|
||||||
* href="http://www.cs.rochester.edu/u/michael/PODC96.html"> Simple,
|
|
||||||
* Fast, and Practical Non-Blocking and Blocking Concurrent Queue
|
|
||||||
* Algorithms</a> by Maged M. Michael and Michael L. Scott.
|
|
||||||
*
|
|
||||||
* <p>Iterators are <i>weakly consistent</i>, returning elements
|
|
||||||
* reflecting the state of the queue at some point at or since the
|
|
||||||
* creation of the iterator. They do <em>not</em> throw {@link
|
|
||||||
* java.util.ConcurrentModificationException}, and may proceed concurrently
|
|
||||||
* with other operations. Elements contained in the queue since the creation
|
|
||||||
* of the iterator will be returned exactly once.
|
|
||||||
*
|
|
||||||
* <p>Beware that, unlike in most collections, the {@code size} method
|
|
||||||
* is <em>NOT</em> a constant-time operation. Because of the
|
|
||||||
* asynchronous nature of these queues, determining the current number
|
|
||||||
* of elements requires a traversal of the elements, and so may report
|
|
||||||
* inaccurate results if this collection is modified during traversal.
|
|
||||||
* Additionally, the bulk operations {@code addAll},
|
|
||||||
* {@code removeAll}, {@code retainAll}, {@code containsAll},
|
|
||||||
* {@code equals}, and {@code toArray} are <em>not</em> guaranteed
|
|
||||||
* to be performed atomically. For example, an iterator operating
|
|
||||||
* concurrently with an {@code addAll} operation might view only some
|
|
||||||
* of the added elements.
|
|
||||||
*
|
|
||||||
* <p>This class and its iterator implement all of the <em>optional</em>
|
|
||||||
* methods of the {@link Queue} and {@link Iterator} interfaces.
|
|
||||||
*
|
|
||||||
* <p>Memory consistency effects: As with other concurrent
|
|
||||||
* collections, actions in a thread prior to placing an object into a
|
|
||||||
* {@code ConcurrentLinkedQueue}
|
|
||||||
* <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
|
|
||||||
* actions subsequent to the access or removal of that element from
|
|
||||||
* the {@code ConcurrentLinkedQueue} in another thread.
|
|
||||||
*
|
|
||||||
* <p>This class is a member of the
|
|
||||||
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
|
|
||||||
* Java Collections Framework</a>.
|
|
||||||
*
|
|
||||||
* @since 1.5
|
|
||||||
* @author Doug Lea
|
|
||||||
* @param <E> the type of elements held in this collection
|
|
||||||
*/
|
|
||||||
public class ConcurrentLinkedQueue2<E> extends AbstractQueue<E>
|
|
||||||
implements Queue<E>, java.io.Serializable {
|
|
||||||
private static final long serialVersionUID = 196745693267521676L;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is a modification of the Michael & Scott algorithm,
|
|
||||||
* adapted for a garbage-collected environment, with support for
|
|
||||||
* interior node deletion (to support remove(Object)). For
|
|
||||||
* explanation, read the paper.
|
|
||||||
*
|
|
||||||
* Note that like most non-blocking algorithms in this package,
|
|
||||||
* this implementation relies on the fact that in garbage
|
|
||||||
* collected systems, there is no possibility of ABA problems due
|
|
||||||
* to recycled nodes, so there is no need to use "counted
|
|
||||||
* pointers" or related techniques seen in versions used in
|
|
||||||
* non-GC'ed settings.
|
|
||||||
*
|
|
||||||
* The fundamental invariants are:
|
|
||||||
* - There is exactly one (last) Node with a null next reference,
|
|
||||||
* which is CASed when enqueueing. This last Node can be
|
|
||||||
* reached in O(1) time from tail, but tail is merely an
|
|
||||||
* optimization - it can always be reached in O(N) time from
|
|
||||||
* head as well.
|
|
||||||
* - The elements contained in the queue are the non-null items in
|
|
||||||
* Nodes that are reachable from head. CASing the item
|
|
||||||
* reference of a Node to null atomically removes it from the
|
|
||||||
* queue. Reachability of all elements from head must remain
|
|
||||||
* true even in the case of concurrent modifications that cause
|
|
||||||
* head to advance. A dequeued Node may remain in use
|
|
||||||
* indefinitely due to creation of an Iterator or simply a
|
|
||||||
* poll() that has lost its time slice.
|
|
||||||
*
|
|
||||||
* The above might appear to imply that all Nodes are GC-reachable
|
|
||||||
* from a predecessor dequeued Node. That would cause two problems:
|
|
||||||
* - allow a rogue Iterator to cause unbounded memory retention
|
|
||||||
* - cause cross-generational linking of old Nodes to new Nodes if
|
|
||||||
* a Node was tenured while live, which generational GCs have a
|
|
||||||
* hard time dealing with, causing repeated major collections.
|
|
||||||
* However, only non-deleted Nodes need to be reachable from
|
|
||||||
* dequeued Nodes, and reachability does not necessarily have to
|
|
||||||
* be of the kind understood by the GC. We use the trick of
|
|
||||||
* linking a Node that has just been dequeued to itself. Such a
|
|
||||||
* self-link implicitly means to advance to head.
|
|
||||||
*
|
|
||||||
* Both head and tail are permitted to lag. In fact, failing to
|
|
||||||
* update them every time one could is a significant optimization
|
|
||||||
* (fewer CASes). As with LinkedTransferQueue (see the internal
|
|
||||||
* documentation for that class), we use a slack threshold of two;
|
|
||||||
* that is, we update head/tail when the current pointer appears
|
|
||||||
* to be two or more steps away from the first/last node.
|
|
||||||
*
|
|
||||||
* Since head and tail are updated concurrently and independently,
|
|
||||||
* it is possible for tail to lag behind head (why not)?
|
|
||||||
*
|
|
||||||
* CASing a Node's item reference to null atomically removes the
|
|
||||||
* element from the queue. Iterators skip over Nodes with null
|
|
||||||
* items. Prior implementations of this class had a race between
|
|
||||||
* poll() and remove(Object) where the same element would appear
|
|
||||||
* to be successfully removed by two concurrent operations. The
|
|
||||||
* method remove(Object) also lazily unlinks deleted Nodes, but
|
|
||||||
* this is merely an optimization.
|
|
||||||
*
|
|
||||||
* When constructing a Node (before enqueuing it) we avoid paying
|
|
||||||
* for a volatile write to item by using Unsafe.putObject instead
|
|
||||||
* of a normal write. This allows the cost of enqueue to be
|
|
||||||
* "one-and-a-half" CASes.
|
|
||||||
*
|
|
||||||
* Both head and tail may or may not point to a Node with a
|
|
||||||
* non-null item. If the queue is empty, all items must of course
|
|
||||||
* be null. Upon creation, both head and tail refer to a dummy
|
|
||||||
* Node with null item. Both head and tail are only updated using
|
|
||||||
* CAS, so they never regress, although again this is merely an
|
|
||||||
* optimization.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static class Node<E> {
|
|
||||||
volatile E item;
|
|
||||||
volatile Node<E> next;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new node. Uses relaxed write because item can
|
|
||||||
* only be seen after publication via casNext.
|
|
||||||
*/
|
|
||||||
Node(E item) {
|
|
||||||
UNSAFE.putObject(this, itemOffset, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean casItem(E cmp, E val) {
|
|
||||||
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lazySetNext(Node<E> val) {
|
|
||||||
UNSAFE.putOrderedObject(this, nextOffset, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean casNext(Node<E> cmp, Node<E> val) {
|
|
||||||
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsafe mechanics
|
|
||||||
|
|
||||||
private static final sun.misc.Unsafe UNSAFE;
|
|
||||||
private static final long itemOffset;
|
|
||||||
private static final long nextOffset;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
UNSAFE = sun.misc.Unsafe.getUnsafe();
|
|
||||||
Class<?> k = Node.class;
|
|
||||||
itemOffset = UNSAFE.objectFieldOffset
|
|
||||||
(k.getDeclaredField("item"));
|
|
||||||
nextOffset = UNSAFE.objectFieldOffset
|
|
||||||
(k.getDeclaredField("next"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node from which the first live (non-deleted) node (if any)
|
|
||||||
* can be reached in O(1) time.
|
|
||||||
* Invariants:
|
|
||||||
* - all live nodes are reachable from head via succ()
|
|
||||||
* - head != null
|
|
||||||
* - (tmp = head).next != tmp || tmp != head
|
|
||||||
* Non-invariants:
|
|
||||||
* - head.item may or may not be null.
|
|
||||||
* - it is permitted for tail to lag behind head, that is, for tail
|
|
||||||
* to not be reachable from head!
|
|
||||||
*/
|
|
||||||
private transient volatile Node<E> head;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node from which the last node on list (that is, the unique
|
|
||||||
* node with node.next == null) can be reached in O(1) time.
|
|
||||||
* Invariants:
|
|
||||||
* - the last node is always reachable from tail via succ()
|
|
||||||
* - tail != null
|
|
||||||
* Non-invariants:
|
|
||||||
* - tail.item may or may not be null.
|
|
||||||
* - it is permitted for tail to lag behind head, that is, for tail
|
|
||||||
* to not be reachable from head!
|
|
||||||
* - tail.next may or may not be self-pointing to tail.
|
|
||||||
*/
|
|
||||||
private transient volatile Node<E> tail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@code ConcurrentLinkedQueue} that is initially empty.
|
|
||||||
*/
|
|
||||||
public ConcurrentLinkedQueue2() {
|
|
||||||
head = tail = new Node<E>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@code ConcurrentLinkedQueue}
|
|
||||||
* initially containing the elements of the given collection,
|
|
||||||
* added in traversal order of the collection's iterator.
|
|
||||||
*
|
|
||||||
* @param c the collection of elements to initially contain
|
|
||||||
* @throws NullPointerException if the specified collection or any
|
|
||||||
* of its elements are null
|
|
||||||
*/
|
|
||||||
public ConcurrentLinkedQueue2(Collection<? extends E> c) {
|
|
||||||
Node<E> h = null, t = null;
|
|
||||||
for (E e : c) {
|
|
||||||
checkNotNull(e);
|
|
||||||
Node<E> newNode = new Node<E>(e);
|
|
||||||
if (h == null)
|
|
||||||
h = t = newNode;
|
|
||||||
else {
|
|
||||||
t.lazySetNext(newNode);
|
|
||||||
t = newNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (h == null)
|
|
||||||
h = t = new Node<E>(null);
|
|
||||||
head = h;
|
|
||||||
tail = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have to override just to update the javadoc
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the specified element at the tail of this queue.
|
|
||||||
* As the queue is unbounded, this method will never throw
|
|
||||||
* {@link IllegalStateException} or return {@code false}.
|
|
||||||
*
|
|
||||||
* @return {@code true} (as specified by {@link Collection#add})
|
|
||||||
* @throws NullPointerException if the specified element is null
|
|
||||||
*/
|
|
||||||
public boolean add(E e) {
|
|
||||||
return offer(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to CAS head to p. If successful, repoint old head to itself
|
|
||||||
* as sentinel for succ(), below.
|
|
||||||
*/
|
|
||||||
final void updateHead(Node<E> h, Node<E> p) {
|
|
||||||
if (h != p && casHead(h, p))
|
|
||||||
h.lazySetNext(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the successor of p, or the head node if p.next has been
|
|
||||||
* linked to self, which will only be true if traversing with a
|
|
||||||
* stale pointer that is now off the list.
|
|
||||||
*/
|
|
||||||
final Node<E> succ(Node<E> p) {
|
|
||||||
Node<E> next = p.next;
|
|
||||||
return (p == next) ? head : next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// can ONLY be touched by a single reader/writer (for add/offer/remove/poll/etc)
|
|
||||||
private final IdentityMap<Object, Node<E>> quickLookup = new IdentityMap<Object, Node<E>>(32);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the specified element at the tail of this queue.
|
|
||||||
* As the queue is unbounded, this method will never return {@code false}.
|
|
||||||
*
|
|
||||||
* @return {@code true} (as specified by {@link Queue#offer})
|
|
||||||
* @throws NullPointerException if the specified element is null
|
|
||||||
*/
|
|
||||||
public boolean offer(E e) {
|
|
||||||
checkNotNull(e);
|
|
||||||
final Node<E> newNode = new Node<E>(e);
|
|
||||||
|
|
||||||
for (Node<E> t = tail, p = t;;) {
|
|
||||||
Node<E> q = p.next;
|
|
||||||
if (q == null) {
|
|
||||||
// p is last node
|
|
||||||
if (p.casNext(null, newNode)) {
|
|
||||||
// Successful CAS is the linearization point
|
|
||||||
// for e to become an element of this queue,
|
|
||||||
// and for newNode to become "live".
|
|
||||||
if (p != t) // hop two nodes at a time
|
|
||||||
casTail(t, newNode); // Failure is OK.
|
|
||||||
|
|
||||||
quickLookup.put(e, newNode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Lost CAS race to another thread; re-read next
|
|
||||||
}
|
|
||||||
else if (p == q)
|
|
||||||
// We have fallen off list. If tail is unchanged, it
|
|
||||||
// will also be off-list, in which case we need to
|
|
||||||
// jump to head, from which all live nodes are always
|
|
||||||
// reachable. Else the new tail is a better bet.
|
|
||||||
p = (t != (t = tail)) ? t : head;
|
|
||||||
else
|
|
||||||
// Check for tail updates after two hops.
|
|
||||||
p = (p != t && t != (t = tail)) ? t : q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public E poll() {
|
|
||||||
restartFromHead:
|
|
||||||
for (;;) {
|
|
||||||
for (Node<E> h = head, p = h, q;;) {
|
|
||||||
E item = p.item;
|
|
||||||
|
|
||||||
if (item != null && p.casItem(item, null)) {
|
|
||||||
// Successful CAS is the linearization point
|
|
||||||
// for item to be removed from this queue.
|
|
||||||
if (p != h) // hop two nodes at a time
|
|
||||||
updateHead(h, ((q = p.next) != null) ? q : p);
|
|
||||||
|
|
||||||
quickLookup.remove(item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
else if ((q = p.next) == null) {
|
|
||||||
updateHead(h, p);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else if (p == q)
|
|
||||||
continue restartFromHead;
|
|
||||||
else
|
|
||||||
p = q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public E peek() {
|
|
||||||
restartFromHead:
|
|
||||||
for (;;) {
|
|
||||||
for (Node<E> h = head, p = h, q;;) {
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null || (q = p.next) == null) {
|
|
||||||
updateHead(h, p);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
else if (p == q)
|
|
||||||
continue restartFromHead;
|
|
||||||
else
|
|
||||||
p = q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first live (non-deleted) node on list, or null if none.
|
|
||||||
* This is yet another variant of poll/peek; here returning the
|
|
||||||
* first node, not element. We could make peek() a wrapper around
|
|
||||||
* first(), but that would cost an extra volatile read of item,
|
|
||||||
* and the need to add a retry loop to deal with the possibility
|
|
||||||
* of losing a race to a concurrent poll().
|
|
||||||
*/
|
|
||||||
Node<E> first() {
|
|
||||||
restartFromHead:
|
|
||||||
for (;;) {
|
|
||||||
for (Node<E> h = head, p = h, q;;) {
|
|
||||||
boolean hasItem = (p.item != null);
|
|
||||||
if (hasItem || (q = p.next) == null) {
|
|
||||||
updateHead(h, p);
|
|
||||||
return hasItem ? p : null;
|
|
||||||
}
|
|
||||||
else if (p == q)
|
|
||||||
continue restartFromHead;
|
|
||||||
else
|
|
||||||
p = q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this queue contains no elements.
|
|
||||||
*
|
|
||||||
* @return {@code true} if this queue contains no elements
|
|
||||||
*/
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return first() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of elements in this queue. If this queue
|
|
||||||
* contains more than {@code Integer.MAX_VALUE} elements, returns
|
|
||||||
* {@code Integer.MAX_VALUE}.
|
|
||||||
*
|
|
||||||
* <p>Beware that, unlike in most collections, this method is
|
|
||||||
* <em>NOT</em> a constant-time operation. Because of the
|
|
||||||
* asynchronous nature of these queues, determining the current
|
|
||||||
* number of elements requires an O(n) traversal.
|
|
||||||
* Additionally, if elements are added or removed during execution
|
|
||||||
* of this method, the returned result may be inaccurate. Thus,
|
|
||||||
* this method is typically not very useful in concurrent
|
|
||||||
* applications.
|
|
||||||
*
|
|
||||||
* @return the number of elements in this queue
|
|
||||||
*/
|
|
||||||
public int size() {
|
|
||||||
int count = 0;
|
|
||||||
for (Node<E> p = first(); p != null; p = succ(p))
|
|
||||||
if (p.item != null)
|
|
||||||
// Collection.size() spec says to max out
|
|
||||||
if (++count == Integer.MAX_VALUE)
|
|
||||||
break;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this queue contains the specified element.
|
|
||||||
* More formally, returns {@code true} if and only if this queue contains
|
|
||||||
* at least one element {@code e} such that {@code o.equals(e)}.
|
|
||||||
*
|
|
||||||
* @param o object to be checked for containment in this queue
|
|
||||||
* @return {@code true} if this queue contains the specified element
|
|
||||||
*/
|
|
||||||
public boolean contains(Object o) {
|
|
||||||
if (o == null) return false;
|
|
||||||
for (Node<E> p = first(); p != null; p = succ(p)) {
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null && o.equals(item))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a single instance of the specified element from this queue,
|
|
||||||
* if it is present. More formally, removes an element {@code e} such
|
|
||||||
* that {@code o.equals(e)}, if this queue contains one or more such
|
|
||||||
* elements.
|
|
||||||
* Returns {@code true} if this queue contained the specified element
|
|
||||||
* (or equivalently, if this queue changed as a result of the call).
|
|
||||||
*
|
|
||||||
* @param o element to be removed from this queue, if present
|
|
||||||
* @return {@code true} if this queue changed as a result of the call
|
|
||||||
*/
|
|
||||||
public boolean remove(Object o) {
|
|
||||||
if (o == null) return false;
|
|
||||||
|
|
||||||
Node<E> pred = null;
|
|
||||||
for (Node<E> p = first(); p != null; p = succ(p)) {
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null &&
|
|
||||||
o.equals(item) &&
|
|
||||||
p.casItem(item, null)) {
|
|
||||||
Node<E> next = succ(p);
|
|
||||||
if (pred != null && next != null)
|
|
||||||
pred.casNext(p, next);
|
|
||||||
|
|
||||||
quickLookup.remove(o);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pred = p;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends all of the elements in the specified collection to the end of
|
|
||||||
* this queue, in the order that they are returned by the specified
|
|
||||||
* collection's iterator. Attempts to {@code addAll} of a queue to
|
|
||||||
* itself result in {@code IllegalArgumentException}.
|
|
||||||
*
|
|
||||||
* @param c the elements to be inserted into this queue
|
|
||||||
* @return {@code true} if this queue changed as a result of the call
|
|
||||||
* @throws NullPointerException if the specified collection or any
|
|
||||||
* of its elements are null
|
|
||||||
* @throws IllegalArgumentException if the collection is this queue
|
|
||||||
*/
|
|
||||||
public boolean addAll(Collection<? extends E> c) {
|
|
||||||
if (c == this)
|
|
||||||
// As historically specified in AbstractQueue#addAll
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
// Copy c into a private chain of Nodes
|
|
||||||
Node<E> beginningOfTheEnd = null, last = null;
|
|
||||||
for (E e : c) {
|
|
||||||
checkNotNull(e);
|
|
||||||
Node<E> newNode = new Node<E>(e);
|
|
||||||
if (beginningOfTheEnd == null)
|
|
||||||
beginningOfTheEnd = last = newNode;
|
|
||||||
else {
|
|
||||||
last.lazySetNext(newNode);
|
|
||||||
last = newNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (beginningOfTheEnd == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Atomically append the chain at the tail of this collection
|
|
||||||
for (Node<E> t = tail, p = t;;) {
|
|
||||||
Node<E> q = p.next;
|
|
||||||
if (q == null) {
|
|
||||||
// p is last node
|
|
||||||
if (p.casNext(null, beginningOfTheEnd)) {
|
|
||||||
// Successful CAS is the linearization point
|
|
||||||
// for all elements to be added to this queue.
|
|
||||||
if (!casTail(t, last)) {
|
|
||||||
// Try a little harder to update tail,
|
|
||||||
// since we may be adding many elements.
|
|
||||||
t = tail;
|
|
||||||
if (last.next == null)
|
|
||||||
casTail(t, last);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Lost CAS race to another thread; re-read next
|
|
||||||
}
|
|
||||||
else if (p == q)
|
|
||||||
// We have fallen off list. If tail is unchanged, it
|
|
||||||
// will also be off-list, in which case we need to
|
|
||||||
// jump to head, from which all live nodes are always
|
|
||||||
// reachable. Else the new tail is a better bet.
|
|
||||||
p = (t != (t = tail)) ? t : head;
|
|
||||||
else
|
|
||||||
// Check for tail updates after two hops.
|
|
||||||
p = (p != t && t != (t = tail)) ? t : q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array containing all of the elements in this queue, in
|
|
||||||
* proper sequence.
|
|
||||||
*
|
|
||||||
* <p>The returned array will be "safe" in that no references to it are
|
|
||||||
* maintained by this queue. (In other words, this method must allocate
|
|
||||||
* a new array). The caller is thus free to modify the returned array.
|
|
||||||
*
|
|
||||||
* <p>This method acts as bridge between array-based and collection-based
|
|
||||||
* APIs.
|
|
||||||
*
|
|
||||||
* @return an array containing all of the elements in this queue
|
|
||||||
*/
|
|
||||||
public Object[] toArray() {
|
|
||||||
// Use ArrayList to deal with resizing.
|
|
||||||
ArrayList<E> al = new ArrayList<E>();
|
|
||||||
for (Node<E> p = first(); p != null; p = succ(p)) {
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null)
|
|
||||||
al.add(item);
|
|
||||||
}
|
|
||||||
return al.toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array containing all of the elements in this queue, in
|
|
||||||
* proper sequence; the runtime type of the returned array is that of
|
|
||||||
* the specified array. If the queue fits in the specified array, it
|
|
||||||
* is returned therein. Otherwise, a new array is allocated with the
|
|
||||||
* runtime type of the specified array and the size of this queue.
|
|
||||||
*
|
|
||||||
* <p>If this queue fits in the specified array with room to spare
|
|
||||||
* (i.e., the array has more elements than this queue), the element in
|
|
||||||
* the array immediately following the end of the queue is set to
|
|
||||||
* {@code null}.
|
|
||||||
*
|
|
||||||
* <p>Like the {@link #toArray()} method, this method acts as bridge between
|
|
||||||
* array-based and collection-based APIs. Further, this method allows
|
|
||||||
* precise control over the runtime type of the output array, and may,
|
|
||||||
* under certain circumstances, be used to save allocation costs.
|
|
||||||
*
|
|
||||||
* <p>Suppose {@code x} is a queue known to contain only strings.
|
|
||||||
* The following code can be used to dump the queue into a newly
|
|
||||||
* allocated array of {@code String}:
|
|
||||||
*
|
|
||||||
* <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
|
|
||||||
*
|
|
||||||
* Note that {@code toArray(new Object[0])} is identical in function to
|
|
||||||
* {@code toArray()}.
|
|
||||||
*
|
|
||||||
* @param a the array into which the elements of the queue are to
|
|
||||||
* be stored, if it is big enough; otherwise, a new array of the
|
|
||||||
* same runtime type is allocated for this purpose
|
|
||||||
* @return an array containing all of the elements in this queue
|
|
||||||
* @throws ArrayStoreException if the runtime type of the specified array
|
|
||||||
* is not a supertype of the runtime type of every element in
|
|
||||||
* this queue
|
|
||||||
* @throws NullPointerException if the specified array is null
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> T[] toArray(T[] a) {
|
|
||||||
// try to use sent-in array
|
|
||||||
int k = 0;
|
|
||||||
Node<E> p;
|
|
||||||
for (p = first(); p != null && k < a.length; p = succ(p)) {
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null)
|
|
||||||
a[k++] = (T)item;
|
|
||||||
}
|
|
||||||
if (p == null) {
|
|
||||||
if (k < a.length)
|
|
||||||
a[k] = null;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If won't fit, use ArrayList version
|
|
||||||
ArrayList<E> al = new ArrayList<E>();
|
|
||||||
for (Node<E> q = first(); q != null; q = succ(q)) {
|
|
||||||
E item = q.item;
|
|
||||||
if (item != null)
|
|
||||||
al.add(item);
|
|
||||||
}
|
|
||||||
return al.toArray(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator over the elements in this queue in proper sequence.
|
|
||||||
* The elements will be returned in order from first (head) to last (tail).
|
|
||||||
*
|
|
||||||
* <p>The returned iterator is
|
|
||||||
* <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
|
|
||||||
*
|
|
||||||
* @return an iterator over the elements in this queue in proper sequence
|
|
||||||
*/
|
|
||||||
public Iterator<E> iterator() {
|
|
||||||
return new Itr();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Itr implements Iterator<E> {
|
|
||||||
/**
|
|
||||||
* Next node to return item for.
|
|
||||||
*/
|
|
||||||
private Node<E> nextNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* nextItem holds on to item fields because once we claim
|
|
||||||
* that an element exists in hasNext(), we must return it in
|
|
||||||
* the following next() call even if it was in the process of
|
|
||||||
* being removed when hasNext() was called.
|
|
||||||
*/
|
|
||||||
private E nextItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Node of the last returned item, to support remove.
|
|
||||||
*/
|
|
||||||
private Node<E> lastRet;
|
|
||||||
|
|
||||||
Itr() {
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves to next valid node and returns item to return for
|
|
||||||
* next(), or null if no such.
|
|
||||||
*/
|
|
||||||
private E advance() {
|
|
||||||
lastRet = nextNode;
|
|
||||||
E x = nextItem;
|
|
||||||
|
|
||||||
Node<E> pred, p;
|
|
||||||
if (nextNode == null) {
|
|
||||||
p = first();
|
|
||||||
pred = null;
|
|
||||||
} else {
|
|
||||||
pred = nextNode;
|
|
||||||
p = succ(nextNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
if (p == null) {
|
|
||||||
nextNode = null;
|
|
||||||
nextItem = null;
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
E item = p.item;
|
|
||||||
if (item != null) {
|
|
||||||
nextNode = p;
|
|
||||||
nextItem = item;
|
|
||||||
return x;
|
|
||||||
} else {
|
|
||||||
// skip over nulls
|
|
||||||
Node<E> next = succ(p);
|
|
||||||
if (pred != null && next != null)
|
|
||||||
pred.casNext(p, next);
|
|
||||||
p = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasNext() {
|
|
||||||
return nextNode != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public E next() {
|
|
||||||
if (nextNode == null) throw new NoSuchElementException();
|
|
||||||
return advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove() {
|
|
||||||
Node<E> l = lastRet;
|
|
||||||
if (l == null) throw new IllegalStateException();
|
|
||||||
// rely on a future traversal to relink.
|
|
||||||
l.item = null;
|
|
||||||
lastRet = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves this queue to a stream (that is, serializes it).
|
|
||||||
*
|
|
||||||
* @param s the stream
|
|
||||||
* @throws java.io.IOException if an I/O error occurs
|
|
||||||
* @serialData All of the elements (each an {@code E}) in
|
|
||||||
* the proper order, followed by a null
|
|
||||||
*/
|
|
||||||
private void writeObject(java.io.ObjectOutputStream s)
|
|
||||||
throws java.io.IOException {
|
|
||||||
|
|
||||||
// Write out any hidden stuff
|
|
||||||
s.defaultWriteObject();
|
|
||||||
|
|
||||||
// Write out all elements in the proper order.
|
|
||||||
for (Node<E> p = first(); p != null; p = succ(p)) {
|
|
||||||
Object item = p.item;
|
|
||||||
if (item != null)
|
|
||||||
s.writeObject(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use trailing null as sentinel
|
|
||||||
s.writeObject(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reconstitutes this queue from a stream (that is, deserializes it).
|
|
||||||
* @param s the stream
|
|
||||||
* @throws ClassNotFoundException if the class of a serialized object
|
|
||||||
* could not be found
|
|
||||||
* @throws java.io.IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
private void readObject(java.io.ObjectInputStream s)
|
|
||||||
throws java.io.IOException, ClassNotFoundException {
|
|
||||||
s.defaultReadObject();
|
|
||||||
|
|
||||||
// Read in elements until trailing null sentinel found
|
|
||||||
Node<E> h = null, t = null;
|
|
||||||
Object item;
|
|
||||||
while ((item = s.readObject()) != null) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Node<E> newNode = new Node<E>((E) item);
|
|
||||||
if (h == null)
|
|
||||||
h = t = newNode;
|
|
||||||
else {
|
|
||||||
t.lazySetNext(newNode);
|
|
||||||
t = newNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (h == null)
|
|
||||||
h = t = new Node<E>(null);
|
|
||||||
head = h;
|
|
||||||
tail = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A customized variant of Spliterators.IteratorSpliterator */
|
|
||||||
static final class CLQSpliterator<E> implements Spliterator<E> {
|
|
||||||
static final int MAX_BATCH = 1 << 25; // max batch array size;
|
|
||||||
final ConcurrentLinkedQueue2<E> queue;
|
|
||||||
Node<E> current; // current node; null until initialized
|
|
||||||
int batch; // batch size for splits
|
|
||||||
boolean exhausted; // true when no more nodes
|
|
||||||
CLQSpliterator(ConcurrentLinkedQueue2<E> queue) {
|
|
||||||
this.queue = queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Spliterator<E> trySplit() {
|
|
||||||
Node<E> p;
|
|
||||||
final ConcurrentLinkedQueue2<E> q = this.queue;
|
|
||||||
int b = batch;
|
|
||||||
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1;
|
|
||||||
if (!exhausted &&
|
|
||||||
((p = current) != null || (p = q.first()) != null) &&
|
|
||||||
p.next != null) {
|
|
||||||
Object[] a = new Object[n];
|
|
||||||
int i = 0;
|
|
||||||
do {
|
|
||||||
if ((a[i] = p.item) != null)
|
|
||||||
++i;
|
|
||||||
if (p == (p = p.next))
|
|
||||||
p = q.first();
|
|
||||||
} while (p != null && i < n);
|
|
||||||
if ((current = p) == null)
|
|
||||||
exhausted = true;
|
|
||||||
if (i > 0) {
|
|
||||||
batch = i;
|
|
||||||
return Spliterators.spliterator
|
|
||||||
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
|
|
||||||
Spliterator.CONCURRENT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forEachRemaining(Consumer<? super E> action) {
|
|
||||||
Node<E> p;
|
|
||||||
if (action == null) throw new NullPointerException();
|
|
||||||
final ConcurrentLinkedQueue2<E> q = this.queue;
|
|
||||||
if (!exhausted &&
|
|
||||||
((p = current) != null || (p = q.first()) != null)) {
|
|
||||||
exhausted = true;
|
|
||||||
do {
|
|
||||||
E e = p.item;
|
|
||||||
if (p == (p = p.next))
|
|
||||||
p = q.first();
|
|
||||||
if (e != null)
|
|
||||||
action.accept(e);
|
|
||||||
} while (p != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean tryAdvance(Consumer<? super E> action) {
|
|
||||||
Node<E> p;
|
|
||||||
if (action == null) throw new NullPointerException();
|
|
||||||
final ConcurrentLinkedQueue2<E> q = this.queue;
|
|
||||||
if (!exhausted &&
|
|
||||||
((p = current) != null || (p = q.first()) != null)) {
|
|
||||||
E e;
|
|
||||||
do {
|
|
||||||
e = p.item;
|
|
||||||
if (p == (p = p.next))
|
|
||||||
p = q.first();
|
|
||||||
} while (e == null && p != null);
|
|
||||||
if ((current = p) == null)
|
|
||||||
exhausted = true;
|
|
||||||
if (e != null) {
|
|
||||||
action.accept(e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long estimateSize() { return Long.MAX_VALUE; }
|
|
||||||
|
|
||||||
public int characteristics() {
|
|
||||||
return Spliterator.ORDERED | Spliterator.NONNULL |
|
|
||||||
Spliterator.CONCURRENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Spliterator} over the elements in this queue.
|
|
||||||
*
|
|
||||||
* <p>The returned spliterator is
|
|
||||||
* <a href="package-summary.html#Weakly"><i>weakly consistent</i></a>.
|
|
||||||
*
|
|
||||||
* <p>The {@code Spliterator} reports {@link Spliterator#CONCURRENT},
|
|
||||||
* {@link Spliterator#ORDERED}, and {@link Spliterator#NONNULL}.
|
|
||||||
*
|
|
||||||
* @implNote
|
|
||||||
* The {@code Spliterator} implements {@code trySplit} to permit limited
|
|
||||||
* parallelism.
|
|
||||||
*
|
|
||||||
* @return a {@code Spliterator} over the elements in this queue
|
|
||||||
* @since 1.8
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Spliterator<E> spliterator() {
|
|
||||||
return new CLQSpliterator<E>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws NullPointerException if argument is null.
|
|
||||||
*
|
|
||||||
* @param v the element
|
|
||||||
*/
|
|
||||||
private static void checkNotNull(Object v) {
|
|
||||||
if (v == null)
|
|
||||||
throw new NullPointerException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean casTail(Node<E> cmp, Node<E> val) {
|
|
||||||
return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean casHead(Node<E> cmp, Node<E> val) {
|
|
||||||
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsafe mechanics
|
|
||||||
|
|
||||||
private static final sun.misc.Unsafe UNSAFE;
|
|
||||||
private static final long headOffset;
|
|
||||||
private static final long tailOffset;
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
UNSAFE = sun.misc.Unsafe.getUnsafe();
|
|
||||||
Class<?> k = ConcurrentLinkedQueue.class;
|
|
||||||
headOffset = UNSAFE.objectFieldOffset
|
|
||||||
(k.getDeclaredField("head"));
|
|
||||||
tailOffset = UNSAFE.objectFieldOffset
|
|
||||||
(k.getDeclaredField("tail"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,5 +21,4 @@ public interface Publisher {
|
||||||
void publish(final Synchrony synchrony, Object message1);
|
void publish(final Synchrony synchrony, Object message1);
|
||||||
void publish(final Synchrony synchrony, Object message1, Object message2);
|
void publish(final Synchrony synchrony, Object message1, Object message2);
|
||||||
void publish(final Synchrony synchrony, Object message1, Object message2, Object message3);
|
void publish(final Synchrony synchrony, Object message1, Object message2, Object message3);
|
||||||
void publish(final Synchrony synchrony, Object[] messages);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,24 +44,14 @@ class PublisherExact implements Publisher {
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
if (subscriptions != null) {
|
if (subscriptions != null) {
|
||||||
Subscription sub;
|
synchrony.publish(subscriptions, message1);
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
if (deadSubscriptions != null) {
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1);
|
synchrony.publish(deadSubscriptions, new DeadMessage(message1));
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -78,33 +68,18 @@ class PublisherExact implements Publisher {
|
||||||
final Class<?> messageClass1 = message1.getClass();
|
final Class<?> messageClass1 = message1.getClass();
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
final Class<?> messageClass2 = message2.getClass();
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
|
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
if (subscriptions != null) {
|
if (subscriptions != null) {
|
||||||
Subscription sub;
|
synchrony.publish(subscriptions, message1, message2);
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1, message2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
if (deadSubscriptions != null) {
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2);
|
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -122,33 +97,18 @@ class PublisherExact implements Publisher {
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
final Class<?> messageClass2 = message2.getClass();
|
||||||
final Class<?> messageClass3 = message3.getClass();
|
final Class<?> messageClass3 = message3.getClass();
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
|
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
if (subscriptions != null) {
|
if (subscriptions != null) {
|
||||||
Subscription sub;
|
synchrony.publish(subscriptions, message1, message2, message3);
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1, message2, message3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
if (deadSubscriptions != null) {
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
|
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -157,10 +117,4 @@ class PublisherExact implements Publisher {
|
||||||
.setPublishedObject(message1, message2, message3));
|
.setPublishedObject(message1, message2, message3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object[] messages) {
|
|
||||||
publish(synchrony, (Object) messages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,9 @@ class PublisherExactWithSuperTypes implements Publisher {
|
||||||
synchrony.publish(subscriptions, message1);
|
synchrony.publish(subscriptions, message1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run superClasses
|
// Run superSubscriptions
|
||||||
final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // can return null
|
final Subscription[] superSubscriptions = subManager.getSuperSubs(message1Class); // NOT return null
|
||||||
if (superSubscriptions != null) {
|
if (superSubscriptions.length > 0) {
|
||||||
hasSubs = true;
|
hasSubs = true;
|
||||||
synchrony.publish(superSubscriptions, message1);
|
synchrony.publish(superSubscriptions, message1);
|
||||||
}
|
}
|
||||||
|
@ -80,34 +80,31 @@ class PublisherExactWithSuperTypes implements Publisher {
|
||||||
try {
|
try {
|
||||||
final Class<?> messageClass1 = message1.getClass();
|
final Class<?> messageClass1 = message1.getClass();
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
final Class<?> messageClass2 = message2.getClass();
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
|
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2); // can return null
|
||||||
if (subscriptions != null) {
|
if (subscriptions != null) {
|
||||||
Subscription sub;
|
hasSubs = true;
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
synchrony.publish(subscriptions, message1, message2);
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1, message2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
|
||||||
|
// Run superSubscriptions
|
||||||
|
final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2); // can return null
|
||||||
|
if (superSubscriptions != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
synchrony.publish(superSubscriptions, message1, message2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run dead message subscriptions
|
||||||
|
if (!hasSubs) {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
if (deadSubscriptions != null) {
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2);
|
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2));
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -124,36 +121,31 @@ class PublisherExactWithSuperTypes implements Publisher {
|
||||||
final Class<?> messageClass1 = message1.getClass();
|
final Class<?> messageClass1 = message1.getClass();
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
final Class<?> messageClass2 = message2.getClass();
|
||||||
final Class<?> messageClass3 = message3.getClass();
|
final Class<?> messageClass3 = message3.getClass();
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2,
|
|
||||||
messageClass3); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
// Run subscriptions
|
// Run subscriptions
|
||||||
|
final Subscription[] subscriptions = subManager.getSubs(messageClass1, messageClass2, messageClass3); // can return null
|
||||||
if (subscriptions != null) {
|
if (subscriptions != null) {
|
||||||
Subscription sub;
|
hasSubs = true;
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
synchrony.publish(subscriptions, message1, message2, message3);
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1, message2, message3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
|
||||||
|
// Run superSubscriptions
|
||||||
|
final Subscription[] superSubscriptions = subManager.getSuperSubs(messageClass1, messageClass2, messageClass3); // can return null
|
||||||
|
if (superSubscriptions != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
synchrony.publish(superSubscriptions, message1, message2, message3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run dead message subscriptions
|
||||||
|
if (!hasSubs) {
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
// Dead Event must EXACTLY MATCH (no subclasses)
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
if (deadSubscriptions != null) {
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
|
synchrony.publish(deadSubscriptions, new DeadMessage(message1, message2, message3));
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
@ -162,10 +154,4 @@ class PublisherExactWithSuperTypes implements Publisher {
|
||||||
.setPublishedObject(message1, message2, message3));
|
.setPublishedObject(message1, message2, message3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object[] messages) {
|
|
||||||
publish(synchrony, (Object) messages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,344 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.publication;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.error.DeadMessage;
|
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|
||||||
import dorkbox.util.messagebus.error.PublicationError;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
|
||||||
import dorkbox.util.messagebus.synchrony.Synchrony;
|
|
||||||
import dorkbox.util.messagebus.utils.VarArgUtils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
class PublisherExactWithSuperTypesAndVarity implements Publisher {
|
|
||||||
private final ErrorHandlingSupport errorHandler;
|
|
||||||
|
|
||||||
private final SubscriptionManager subManager;
|
|
||||||
|
|
||||||
private final AtomicBoolean varArgPossibility;
|
|
||||||
final VarArgUtils varArgUtils;
|
|
||||||
|
|
||||||
public
|
|
||||||
PublisherExactWithSuperTypesAndVarity(final ErrorHandlingSupport errorHandler, final SubscriptionManager subManager) {
|
|
||||||
this.errorHandler = errorHandler;
|
|
||||||
this.subManager = subManager;
|
|
||||||
|
|
||||||
varArgPossibility = subManager.getVarArgPossibility();
|
|
||||||
varArgUtils = subManager.getVarArgUtils();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object message1) {
|
|
||||||
try {
|
|
||||||
final Class<?> messageClass = message1.getClass();
|
|
||||||
final boolean isArray = messageClass.isArray();
|
|
||||||
|
|
||||||
Subscription[] subscriptions = subManager.getSubs(messageClass); // can return null
|
|
||||||
|
|
||||||
boolean hasSubs = false;
|
|
||||||
// Run subscriptions
|
|
||||||
if (subscriptions != null) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
synchrony.publish(subscriptions, message1);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriptions = subManager.getSuperSubs(messageClass); // can return null
|
|
||||||
|
|
||||||
|
|
||||||
// publish to var arg, only if not already an array (because that would be unnecessary)
|
|
||||||
if (varArgPossibility.get() && !isArray) {
|
|
||||||
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass, subManager); // CAN NOT RETURN NULL
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
int length = varArgSubs.length;
|
|
||||||
Object[] asArray = null;
|
|
||||||
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
asArray = (Object[]) Array.newInstance(messageClass, 1);
|
|
||||||
asArray[0] = message1;
|
|
||||||
|
|
||||||
synchrony.publish(varArgSubs, asArray);
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSubs[i];
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// now publish array based superClasses (but only if those ALSO accept vararg)
|
|
||||||
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass, subManager); // CAN NOT RETURN NULL
|
|
||||||
|
|
||||||
length = varArgSuperSubs.length;
|
|
||||||
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
if (asArray == null) {
|
|
||||||
asArray = (Object[]) Array.newInstance(messageClass, 1);
|
|
||||||
asArray[0] = message1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSuperSubs[i];
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only get here if there were no other subscriptions
|
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
|
||||||
if (!hasSubs) {
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1);
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object message1, final Object message2) {
|
|
||||||
try {
|
|
||||||
final Class<?> messageClass1 = message1.getClass();
|
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subscriptions = subManager.getExactAndSuper(messageClass1, messageClass2); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
boolean hasSubs = false;
|
|
||||||
// Run subscriptions
|
|
||||||
if (subscriptions != null) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1, message2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// publish to var arg, only if not already an array AND we are all of the same type
|
|
||||||
if (varArgPossibility.get() && !messageClass1.isArray() && !messageClass2.isArray()) {
|
|
||||||
|
|
||||||
// vararg can ONLY work if all types are the same
|
|
||||||
if (messageClass1 == messageClass2) {
|
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass1, subManager); // can NOT return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
final int length = varArgSubs.length;
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Object[] asArray = (Object[]) Array.newInstance(messageClass1, 2);
|
|
||||||
asArray[0] = message1;
|
|
||||||
asArray[1] = message2;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSubs[i];
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now publish array based superClasses (but only if those ALSO accept vararg)
|
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, subManager); // CAN NOT RETURN NULL
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
|
|
||||||
final int length = varArgSuperSubs.length;
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Class<?> arrayType;
|
|
||||||
Object[] asArray;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSuperSubs[i];
|
|
||||||
arrayType = sub.getHandler().getVarArgClass();
|
|
||||||
|
|
||||||
asArray = (Object[]) Array.newInstance(arrayType, 2);
|
|
||||||
asArray[0] = message1;
|
|
||||||
asArray[1] = message2;
|
|
||||||
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasSubs) {
|
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2);
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message1, message2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object message1, final Object message2, final Object message3) {
|
|
||||||
try {
|
|
||||||
final Class<?> messageClass1 = message1.getClass();
|
|
||||||
final Class<?> messageClass2 = message2.getClass();
|
|
||||||
final Class<?> messageClass3 = message3.getClass();
|
|
||||||
|
|
||||||
// final StampedLock lock = this.lock;
|
|
||||||
// long stamp = lock.readLock();
|
|
||||||
final Subscription[] subs = subManager.getExactAndSuper(messageClass1, messageClass2, messageClass3); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
|
|
||||||
boolean hasSubs = false;
|
|
||||||
// Run subscriptions
|
|
||||||
if (subs != null) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < subs.length; i++) {
|
|
||||||
sub = subs[i];
|
|
||||||
sub.publish(message1, message2, message3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// publish to var arg, only if not already an array AND we are all of the same type
|
|
||||||
if (varArgPossibility.get() && !messageClass1.isArray() && !messageClass2.isArray() &&
|
|
||||||
!messageClass3.isArray()) {
|
|
||||||
|
|
||||||
// vararg can ONLY work if all types are the same
|
|
||||||
if (messageClass1 == messageClass2 && messageClass1 == messageClass3) {
|
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] varArgSubs = varArgUtils.getVarArgSubscriptions(messageClass1, subManager); // can NOT return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
final int length = varArgSubs.length;
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Object[] asArray = (Object[]) Array.newInstance(messageClass1, 3);
|
|
||||||
asArray[0] = message1;
|
|
||||||
asArray[1] = message2;
|
|
||||||
asArray[2] = message3;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSubs[i];
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// now publish array based superClasses (but only if those ALSO accept vararg)
|
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] varArgSuperSubs = varArgUtils.getVarArgSuperSubscriptions(messageClass1, messageClass2, messageClass3,
|
|
||||||
subManager); // CAN NOT RETURN NULL
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
|
|
||||||
final int length = varArgSuperSubs.length;
|
|
||||||
if (length > 0) {
|
|
||||||
hasSubs = true;
|
|
||||||
|
|
||||||
Class<?> arrayType;
|
|
||||||
Object[] asArray;
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = varArgSuperSubs[i];
|
|
||||||
arrayType = sub.getHandler().getVarArgClass();
|
|
||||||
|
|
||||||
asArray = (Object[]) Array.newInstance(arrayType, 3);
|
|
||||||
asArray[0] = message1;
|
|
||||||
asArray[1] = message2;
|
|
||||||
asArray[2] = message3;
|
|
||||||
|
|
||||||
sub.publish(asArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasSubs) {
|
|
||||||
// Dead Event must EXACTLY MATCH (no subclasses)
|
|
||||||
// stamp = lock.readLock();
|
|
||||||
final Subscription[] deadSubscriptions = subManager.getSubs(DeadMessage.class); // can return null
|
|
||||||
// lock.unlockRead(stamp);
|
|
||||||
|
|
||||||
if (deadSubscriptions != null) {
|
|
||||||
final DeadMessage deadMessage = new DeadMessage(message1, message2, message3);
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < deadSubscriptions.length; i++) {
|
|
||||||
sub = deadSubscriptions[i];
|
|
||||||
sub.publish(deadMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
errorHandler.handlePublicationError(new PublicationError().setMessage("Error during invocation of message handler.")
|
|
||||||
.setCause(e)
|
|
||||||
.setPublishedObject(message1, message2, message3));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Synchrony synchrony, final Object[] messages) {
|
|
||||||
publish(synchrony, (Object) messages);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package dorkbox.util.messagebus.publication.disruptor;
|
|
||||||
|
|
||||||
import com.lmax.disruptor.EventFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author dorkbox, llc
|
|
||||||
* Date: 2/2/15
|
|
||||||
*/
|
|
||||||
public class EventBusFactory implements EventFactory<MessageHolder> {
|
|
||||||
|
|
||||||
public EventBusFactory() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
MessageHolder newInstance() {
|
|
||||||
return new MessageHolder();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package dorkbox.util.messagebus.publication.disruptor;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author dorkbox, llc Date: 2/2/15
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class MessageHolder {
|
|
||||||
public int type = MessageType.ONE;
|
|
||||||
public Subscription[] subscriptions;
|
|
||||||
|
|
||||||
public Object message1 = null;
|
|
||||||
public Object message2 = null;
|
|
||||||
public Object message3 = null;
|
|
||||||
public Object[] messages = null;
|
|
||||||
|
|
||||||
public
|
|
||||||
MessageHolder() {}
|
|
||||||
}
|
|
26
src/dorkbox/util/messagebus/subscription/SubMaker.java
Normal file
26
src/dorkbox/util/messagebus/subscription/SubMaker.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface SubMaker {
|
||||||
|
Subscription create(final Class<?> listenerClass, final MessageHandler handler);
|
||||||
|
}
|
|
@ -1,27 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012 Benjamin Diedrichsen
|
* Copyright 2016 dorkbox, llc
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
* a copy of this software and associated documentation files (the
|
|
||||||
* "Software"), to deal in the Software without restriction, including
|
|
||||||
* without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
* permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
* the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -38,12 +16,8 @@
|
||||||
package dorkbox.util.messagebus.subscription;
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
|
||||||
import dorkbox.util.messagebus.common.Entry;
|
import dorkbox.util.messagebus.common.Entry;
|
||||||
import dorkbox.util.messagebus.common.MessageHandler;
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
import dorkbox.util.messagebus.dispatch.IHandlerInvocation;
|
|
||||||
import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation;
|
|
||||||
import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
@ -59,11 +33,10 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
|
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
|
||||||
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
|
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
|
||||||
*
|
*
|
||||||
* @author bennidi
|
|
||||||
* @author dorkbox, llc
|
* @author dorkbox, llc
|
||||||
* Date: 2/2/15
|
* Date: 2/3/16
|
||||||
*/
|
*/
|
||||||
public final
|
public abstract
|
||||||
class Subscription {
|
class Subscription {
|
||||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||||
public final int ID = ID_COUNTER.getAndIncrement();
|
public final int ID = ID_COUNTER.getAndIncrement();
|
||||||
|
@ -74,62 +47,53 @@ class Subscription {
|
||||||
|
|
||||||
// the handler's metadata -> for each handler in a listener, a unique subscription context is created
|
// the handler's metadata -> for each handler in a listener, a unique subscription context is created
|
||||||
private final MessageHandler handler;
|
private final MessageHandler handler;
|
||||||
private final IHandlerInvocation invocation;
|
|
||||||
|
|
||||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||||
private static final AtomicReferenceFieldUpdater<Subscription, Entry> headREF =
|
protected static final AtomicReferenceFieldUpdater<Subscription, Entry> headREF =
|
||||||
AtomicReferenceFieldUpdater.newUpdater(Subscription.class,
|
AtomicReferenceFieldUpdater.newUpdater(Subscription.class,
|
||||||
Entry.class,
|
Entry.class,
|
||||||
"head");
|
"head");
|
||||||
|
|
||||||
|
|
||||||
// This is only touched by a single thread!
|
// This is only touched by a single thread!
|
||||||
private final IdentityMap<Object, Entry> entries; // maintain a map of entries for FAST lookup during unsubscribe.
|
private final IdentityMap<Object, Entry> entries; // maintain a map of entries for FAST lookup during unsubscribe.
|
||||||
|
|
||||||
// this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication)
|
// this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication)
|
||||||
public volatile Entry head; // reference to the first element
|
public volatile Entry head; // reference to the first element
|
||||||
|
|
||||||
|
protected
|
||||||
public
|
|
||||||
Subscription(final Class<?> listenerClass, final MessageHandler handler) {
|
Subscription(final Class<?> listenerClass, final MessageHandler handler) {
|
||||||
this.listenerClass = listenerClass;
|
this.listenerClass = listenerClass;
|
||||||
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
|
||||||
IHandlerInvocation invocation = new ReflectiveHandlerInvocation();
|
entries = new IdentityMap<Object, Entry>(32, SubscriptionManager.LOAD_FACTOR);
|
||||||
if (handler.isSynchronized()) {
|
|
||||||
invocation = new SynchronizedHandlerInvocation(invocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.invocation = invocation;
|
|
||||||
|
|
||||||
entries = new IdentityMap<>(32, SubscriptionManager.LOAD_FACTOR);
|
|
||||||
|
|
||||||
|
|
||||||
if (handler.acceptsSubtypes()) {
|
if (handler.acceptsSubtypes()) {
|
||||||
// keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT
|
// TODO keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT
|
||||||
// super-subscriptions to clear when we sub/unsub
|
// super-subscriptions to clear when we sub/unsub
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// called on shutdown for GC purposes
|
// called on shutdown for GC purposes
|
||||||
public void clear() {
|
public final
|
||||||
|
void clear() {
|
||||||
this.entries.clear();
|
this.entries.clear();
|
||||||
this.head.clear();
|
this.head.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// only used in unit tests to verify that the subscription manager is working correctly
|
// only used in unit tests to verify that the subscription manager is working correctly
|
||||||
public
|
public final
|
||||||
Class<?> getListenerClass() {
|
Class<?> getListenerClass() {
|
||||||
return listenerClass;
|
return listenerClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public final
|
||||||
MessageHandler getHandler() {
|
MessageHandler getHandler() {
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public final
|
||||||
void subscribe(final Object listener) {
|
void subscribe(final Object listener) {
|
||||||
// single writer principle!
|
// single writer principle!
|
||||||
Entry head = headREF.get(this);
|
Entry head = headREF.get(this);
|
||||||
|
@ -145,7 +109,7 @@ class Subscription {
|
||||||
/**
|
/**
|
||||||
* @return TRUE if the element was removed
|
* @return TRUE if the element was removed
|
||||||
*/
|
*/
|
||||||
public
|
public final
|
||||||
boolean unsubscribe(final Object listener) {
|
boolean unsubscribe(final Object listener) {
|
||||||
Entry entry = entries.get(listener);
|
Entry entry = entries.get(listener);
|
||||||
if (entry == null || entry.getValue() == null) {
|
if (entry == null || entry.getValue() == null) {
|
||||||
|
@ -174,68 +138,38 @@ class Subscription {
|
||||||
/**
|
/**
|
||||||
* only used in unit tests
|
* only used in unit tests
|
||||||
*/
|
*/
|
||||||
public
|
public final
|
||||||
int size() {
|
int size() {
|
||||||
return this.entries.size;
|
return this.entries.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
/**
|
||||||
void publish(final Object message) throws Throwable {
|
* @return true if messages were published
|
||||||
final MethodAccess handler = this.handler.getHandler();
|
*/
|
||||||
final int handleIndex = this.handler.getMethodIndex();
|
public abstract
|
||||||
final IHandlerInvocation invocation = this.invocation;
|
boolean publish(final Object message) throws Throwable;
|
||||||
|
|
||||||
Entry current = headREF.get(this);
|
/**
|
||||||
Object listener;
|
* @return true if messages were published
|
||||||
while (current != null) {
|
*/
|
||||||
listener = current.getValue();
|
public abstract
|
||||||
current = current.next();
|
boolean publish(final Object message1, final Object message2) throws Throwable;
|
||||||
|
|
||||||
invocation.invoke(listener, handler, handleIndex, message);
|
/**
|
||||||
}
|
* @return true if messages were published
|
||||||
}
|
*/
|
||||||
|
public abstract
|
||||||
public
|
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable;
|
||||||
void publish(final Object message1, final Object message2) throws Throwable {
|
|
||||||
final MethodAccess handler = this.handler.getHandler();
|
|
||||||
final int handleIndex = this.handler.getMethodIndex();
|
|
||||||
final IHandlerInvocation invocation = this.invocation;
|
|
||||||
|
|
||||||
Entry current = headREF.get(this);
|
|
||||||
Object listener;
|
|
||||||
while (current != null) {
|
|
||||||
listener = current.getValue();
|
|
||||||
current = current.next();
|
|
||||||
|
|
||||||
invocation.invoke(listener, handler, handleIndex, message1, message2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void publish(final Object message1, final Object message2, final Object message3) throws Throwable {
|
|
||||||
final MethodAccess handler = this.handler.getHandler();
|
|
||||||
final int handleIndex = this.handler.getMethodIndex();
|
|
||||||
final IHandlerInvocation invocation = this.invocation;
|
|
||||||
|
|
||||||
Entry current = headREF.get(this);
|
|
||||||
Object listener;
|
|
||||||
while (current != null) {
|
|
||||||
listener = current.getValue();
|
|
||||||
current = current.next();
|
|
||||||
|
|
||||||
invocation.invoke(listener, handler, handleIndex, message1, message2, message3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public final
|
||||||
int hashCode() {
|
int hashCode() {
|
||||||
return this.ID;
|
return this.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public final
|
||||||
boolean equals(final Object obj) {
|
boolean equals(final Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -16,22 +16,23 @@
|
||||||
package dorkbox.util.messagebus.subscription;
|
package dorkbox.util.messagebus.subscription;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
import dorkbox.util.messagebus.MessageBus;
|
||||||
import dorkbox.util.messagebus.common.ClassTree;
|
import dorkbox.util.messagebus.common.ClassTree;
|
||||||
import dorkbox.util.messagebus.common.MessageHandler;
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
import dorkbox.util.messagebus.common.MultiClass;
|
import dorkbox.util.messagebus.common.MultiClass;
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
|
import dorkbox.util.messagebus.subscription.asm.SubMakerAsm;
|
||||||
|
import dorkbox.util.messagebus.subscription.reflection.SubMakerReflection;
|
||||||
import dorkbox.util.messagebus.utils.ClassUtils;
|
import dorkbox.util.messagebus.utils.ClassUtils;
|
||||||
import dorkbox.util.messagebus.utils.SubscriptionUtils;
|
import dorkbox.util.messagebus.utils.SubscriptionUtils;
|
||||||
import dorkbox.util.messagebus.utils.VarArgUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
/**
|
/**
|
||||||
* Permits subscriptions with a varying length of parameters as the signature, which must be match by the publisher for it to be accepted
|
* Permits subscriptions with a varying length of parameters as the signature, which must be match by the publisher for it to be accepted
|
||||||
*/
|
*
|
||||||
/**
|
*
|
||||||
* The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process.
|
* The subscription managers responsibility is to consistently handle and synchronize the message listener subscription process.
|
||||||
* It provides fast lookup of existing subscriptions when another instance of an already known
|
* It provides fast lookup of existing subscriptions when another instance of an already known
|
||||||
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
|
* listener is subscribed and takes care of creating new set of subscriptions for any unknown class that defines
|
||||||
|
@ -44,10 +45,12 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
public final
|
public final
|
||||||
class SubscriptionManager {
|
class SubscriptionManager {
|
||||||
public static final float LOAD_FACTOR = 0.8F;
|
public static final float LOAD_FACTOR = 0.8F;
|
||||||
public static final Subscription[] SUBSCRIPTIONS = new Subscription[0];
|
|
||||||
|
|
||||||
// TODO: during startup, pre-calculate the number of subscription listeners and x2 to save as subsPerListener expected max size
|
// TODO: during startup, pre-calculate the number of subscription listeners and x2 to save as subsPerListener expected max size
|
||||||
|
|
||||||
|
// controls if we use java reflection or ASM to access methods during publication
|
||||||
|
private final SubMaker subMaker;
|
||||||
|
|
||||||
|
|
||||||
// ONLY used by SUB/UNSUB
|
// ONLY used by SUB/UNSUB
|
||||||
// remember already processed classes that do not contain any message handlers
|
// remember already processed classes that do not contain any message handlers
|
||||||
|
@ -59,7 +62,6 @@ class SubscriptionManager {
|
||||||
// once a collection of subscriptions is stored it does not change
|
// once a collection of subscriptions is stored it does not change
|
||||||
private final IdentityMap<Class<?>, Subscription[]> subsPerListener;
|
private final IdentityMap<Class<?>, Subscription[]> subsPerListener;
|
||||||
|
|
||||||
|
|
||||||
// We perpetually KEEP the types registered here, and just change what is sub/unsub
|
// We perpetually KEEP the types registered here, and just change what is sub/unsub
|
||||||
|
|
||||||
// all subscriptions of a message type.
|
// all subscriptions of a message type.
|
||||||
|
@ -69,12 +71,6 @@ class SubscriptionManager {
|
||||||
// keeps track of all subscriptions of the super classes of a message type.
|
// keeps track of all subscriptions of the super classes of a message type.
|
||||||
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperSingle;
|
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperSingle;
|
||||||
|
|
||||||
// keeps track of all subscriptions of varity (var-arg) classes of a message type
|
|
||||||
private volatile IdentityMap<Class<?>, Subscription[]> subsVaritySingle;
|
|
||||||
|
|
||||||
// keeps track of all subscriptions of super-class varity (var-arg) classes of a message type
|
|
||||||
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperVaritySingle;
|
|
||||||
|
|
||||||
// In order to force the "Single writer principle" on subscribe & unsubscribe, they are within WRITE LOCKS. They could be dispatched
|
// In order to force the "Single writer principle" on subscribe & unsubscribe, they are within WRITE LOCKS. They could be dispatched
|
||||||
// to another thread, however we do NOT want them asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is
|
// to another thread, however we do NOT want them asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is
|
||||||
// called before. A WriteLock doesn't perform any better here than synchronized does.
|
// called before. A WriteLock doesn't perform any better here than synchronized does.
|
||||||
|
@ -86,13 +82,9 @@ class SubscriptionManager {
|
||||||
private final ErrorHandlingSupport errorHandler;
|
private final ErrorHandlingSupport errorHandler;
|
||||||
|
|
||||||
private final SubscriptionUtils subUtils;
|
private final SubscriptionUtils subUtils;
|
||||||
private final VarArgUtils varArgUtils;
|
|
||||||
|
|
||||||
private final ClassTree<Class<?>> classTree;
|
private final ClassTree<Class<?>> classTree;
|
||||||
|
|
||||||
// shortcut publication if we know there is no possibility of varArg (ie: a method that has an array as arguments)
|
|
||||||
private final AtomicBoolean varArgPossibility = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private final ClassUtils classUtils;
|
private final ClassUtils classUtils;
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,16 +105,6 @@ class SubscriptionManager {
|
||||||
IdentityMap.class,
|
IdentityMap.class,
|
||||||
"subsSuperSingle");
|
"subsSuperSingle");
|
||||||
|
|
||||||
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsVaritySingleREF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
||||||
IdentityMap.class,
|
|
||||||
"subsVaritySingle");
|
|
||||||
|
|
||||||
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsSuperVaritySingleREF =
|
|
||||||
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
||||||
IdentityMap.class,
|
|
||||||
"subsSuperVaritySingle");
|
|
||||||
|
|
||||||
//NOTE for multiArg, can use the memory address concatenated with other ones and then just put it in the 'single" map (convert single to
|
//NOTE for multiArg, can use the memory address concatenated with other ones and then just put it in the 'single" map (convert single to
|
||||||
// use this too). it would likely have to be longs no idea what to do for arrays?? (arrays should verify all the elements are the
|
// use this too). it would likely have to be longs no idea what to do for arrays?? (arrays should verify all the elements are the
|
||||||
// correct type too)
|
// correct type too)
|
||||||
|
@ -131,30 +113,27 @@ class SubscriptionManager {
|
||||||
SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) {
|
SubscriptionManager(final int numberOfThreads, final ErrorHandlingSupport errorHandler) {
|
||||||
this.errorHandler = errorHandler;
|
this.errorHandler = errorHandler;
|
||||||
|
|
||||||
|
if (MessageBus.useAsmForDispatch) {
|
||||||
|
this.subMaker = new SubMakerAsm();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.subMaker = new SubMakerReflection();
|
||||||
|
}
|
||||||
|
|
||||||
classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR);
|
classUtils = new ClassUtils(SubscriptionManager.LOAD_FACTOR);
|
||||||
|
|
||||||
// modified ONLY during SUB/UNSUB
|
// modified ONLY during SUB/UNSUB
|
||||||
nonListeners = new IdentityMap<Class<?>, Boolean>(16, LOAD_FACTOR);
|
nonListeners = new IdentityMap<Class<?>, Boolean>(16, LOAD_FACTOR);
|
||||||
subsPerListener = new IdentityMap<>(32, LOAD_FACTOR);
|
subsPerListener = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
||||||
subsSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
subsSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
||||||
subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
|
subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
|
||||||
|
|
||||||
|
|
||||||
subsSuperSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
subsSuperSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
||||||
subsVaritySingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
|
||||||
subsSuperVaritySingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.classTree = new ClassTree<Class<?>>();
|
|
||||||
|
|
||||||
this.subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads);
|
|
||||||
|
|
||||||
// var arg subscriptions keep track of which subscriptions can handle varArgs. SUB/UNSUB dumps it, so it is recreated dynamically.
|
|
||||||
// it's a hit on SUB/UNSUB, but improves performance of handlers
|
|
||||||
this.varArgUtils = new VarArgUtils(classUtils, LOAD_FACTOR, numberOfThreads);
|
|
||||||
|
|
||||||
|
|
||||||
|
classTree = new ClassTree<Class<?>>();
|
||||||
|
subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads);
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public
|
||||||
|
@ -174,18 +153,14 @@ class SubscriptionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.nonListeners.clear();
|
this.nonListeners.clear();
|
||||||
|
|
||||||
this.subsPerListener.clear();
|
this.subsPerListener.clear();
|
||||||
|
|
||||||
this.subsSingle.clear();
|
this.subsSingle.clear();
|
||||||
this.subsSuperSingle.clear();
|
this.subsSuperSingle.clear();
|
||||||
this.subsVaritySingle.clear();
|
|
||||||
this.subsSuperVaritySingle.clear();
|
|
||||||
|
|
||||||
this.classTree.clear();
|
this.classTree.clear();
|
||||||
|
|
||||||
this.classUtils.shutdown();
|
this.classUtils.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,201 +198,158 @@ class SubscriptionManager {
|
||||||
|
|
||||||
// access a snapshot of the subscriptions (single-writer-principle)
|
// access a snapshot of the subscriptions (single-writer-principle)
|
||||||
final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this);
|
final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this);
|
||||||
// final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.get(this);
|
final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.get(this);
|
||||||
|
|
||||||
// final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
|
|
||||||
// final IdentityMap<Class<?>, Subscription[]> localVaritySubs = subsVaritySingleREF.get(this);
|
|
||||||
// final IdentityMap<Class<?>, Subscription[]> localSuperVaritySubs = subsSuperVaritySingleREF.get(this);
|
|
||||||
|
|
||||||
Subscription subscription;
|
Subscription subscription;
|
||||||
|
|
||||||
MessageHandler messageHandler;
|
MessageHandler messageHandler;
|
||||||
Class<?>[] messageHandlerTypes;
|
Class<?>[] messageHandlerTypes;
|
||||||
|
int messageHandlerTypesSize;
|
||||||
|
|
||||||
|
MultiClass multiClass;
|
||||||
Class<?> handlerType;
|
Class<?> handlerType;
|
||||||
|
|
||||||
|
|
||||||
// Prepare all of the subscriptions
|
// Prepare all of the subscriptions and add for publication AND subscribe since the data structures are consistent
|
||||||
for (int i = 0; i < handlersSize; i++) {
|
for (int i = 0; i < handlersSize; i++) {
|
||||||
// THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE!
|
|
||||||
messageHandler = messageHandlers[i];
|
messageHandler = messageHandlers[i];
|
||||||
|
|
||||||
// is this handler able to accept var args?
|
subscription = subMaker.create(listenerClass, messageHandler);
|
||||||
// if (messageHandler.getVarArgClass() != null) {
|
subscription.subscribe(listener); // register this callback listener to this subscription
|
||||||
// varArgPossibility.lazySet(true);
|
subscriptions[i] = subscription;
|
||||||
// }
|
|
||||||
|
|
||||||
// now create a list of subscriptions for this specific handlerType (but don't add anything yet).
|
// register for publication
|
||||||
// we only store things based on the FIRST type (for lookup) then parse the rest of the types during publication
|
|
||||||
messageHandlerTypes = messageHandler.getHandledMessages();
|
messageHandlerTypes = messageHandler.getHandledMessages();
|
||||||
// final int handlerSize = messageHandlerTypes.length;
|
messageHandlerTypesSize = messageHandlerTypes.length;
|
||||||
// switch (handlerSize) {
|
|
||||||
// case 0: {
|
switch (messageHandlerTypesSize) {
|
||||||
// // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
|
case 0: {
|
||||||
// // This is the SAME THING as having Void as a parameter!!
|
// if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
|
||||||
// handlerType = Void.class;
|
// This is the SAME THING as having Void as a parameter!!
|
||||||
//
|
handlerType = Void.class;
|
||||||
// if (!singleSubs.containsKey(handlerType)) {
|
|
||||||
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
|
|
||||||
// singleSubs.put(handlerType, SUBSCRIPTIONS);
|
// makes this subscription visible for publication
|
||||||
// }
|
final Subscription[] newSubs;
|
||||||
// break;
|
Subscription[] currentSubs = singleSubs.get(handlerType);
|
||||||
// }
|
if (currentSubs != null) {
|
||||||
// case 1: {
|
final int currentLength = currentSubs.length;
|
||||||
|
|
||||||
|
// add the new subscription to the array
|
||||||
|
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
||||||
|
newSubs[currentLength] = subscription;
|
||||||
|
} else {
|
||||||
|
newSubs = new Subscription[1];
|
||||||
|
newSubs[0] = subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
singleSubs.put(handlerType, newSubs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: {
|
||||||
handlerType = messageHandlerTypes[0];
|
handlerType = messageHandlerTypes[0];
|
||||||
|
|
||||||
if (!singleSubs.containsKey(handlerType)) {
|
// makes this subscription visible for publication
|
||||||
// this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
|
final Subscription[] newSubs;
|
||||||
singleSubs.put(handlerType, SUBSCRIPTIONS);
|
Subscription[] currentSubs = singleSubs.get(handlerType);
|
||||||
|
if (currentSubs != null) {
|
||||||
|
final int currentLength = currentSubs.length;
|
||||||
|
|
||||||
|
// add the new subscription to the array
|
||||||
|
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
||||||
|
newSubs[currentLength] = subscription;
|
||||||
|
} else {
|
||||||
|
newSubs = new Subscription[1];
|
||||||
|
newSubs[0] = subscription;
|
||||||
}
|
}
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 2: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
|
|
||||||
// messageHandlerTypes[1]);
|
|
||||||
//
|
|
||||||
// if (!multiSubs.containsKey(multiClass)) {
|
|
||||||
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
|
|
||||||
// multiSubs.put(multiClass, SUBSCRIPTIONS);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 3: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
|
|
||||||
// messageHandlerTypes[1],
|
|
||||||
// messageHandlerTypes[2]);
|
|
||||||
//
|
|
||||||
// if (!multiSubs.containsKey(multiClass)) {
|
|
||||||
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
|
|
||||||
// multiSubs.put(multiClass, SUBSCRIPTIONS);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// default: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes);
|
|
||||||
//
|
|
||||||
// if (!multiSubs.containsKey(multiClass)) {
|
|
||||||
// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added
|
|
||||||
// multiSubs.put(multiClass, SUBSCRIPTIONS);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// create the subscription. This can be thrown away if the subscription succeeds in another thread
|
singleSubs.put(handlerType, newSubs);
|
||||||
subscription = new Subscription(listenerClass, messageHandler);
|
|
||||||
subscriptions[i] = subscription;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1]);
|
||||||
|
|
||||||
|
// makes this subscription visible for publication
|
||||||
|
final Subscription[] newSubs;
|
||||||
|
Subscription[] currentSubs = multiSubs.get(multiClass);
|
||||||
|
|
||||||
|
if (currentSubs != null) {
|
||||||
|
final int currentLength = currentSubs.length;
|
||||||
|
|
||||||
|
// add the new subscription to the array
|
||||||
|
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
||||||
|
newSubs[currentLength] = subscription;
|
||||||
|
} else {
|
||||||
|
newSubs = new Subscription[1];
|
||||||
|
newSubs[0] = subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiSubs.put(multiClass, newSubs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: {
|
||||||
|
multiClass = classTree.get(messageHandlerTypes[0], messageHandlerTypes[1], messageHandlerTypes[2]);
|
||||||
|
|
||||||
|
// makes this subscription visible for publication
|
||||||
|
final Subscription[] newSubs;
|
||||||
|
Subscription[] currentSubs = multiSubs.get(multiClass);
|
||||||
|
|
||||||
|
if (currentSubs != null) {
|
||||||
|
final int currentLength = currentSubs.length;
|
||||||
|
|
||||||
|
// add the new subscription to the array
|
||||||
|
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
||||||
|
newSubs[currentLength] = subscription;
|
||||||
|
} else {
|
||||||
|
newSubs = new Subscription[1];
|
||||||
|
newSubs[0] = subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiSubs.put(multiClass, newSubs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
multiClass = classTree.get(messageHandlerTypes);
|
||||||
|
|
||||||
|
// makes this subscription visible for publication
|
||||||
|
final Subscription[] newSubs;
|
||||||
|
Subscription[] currentSubs = multiSubs.get(multiClass);
|
||||||
|
|
||||||
|
if (currentSubs != null) {
|
||||||
|
final int currentLength = currentSubs.length;
|
||||||
|
|
||||||
|
// add the new subscription to the array
|
||||||
|
newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
||||||
|
newSubs[currentLength] = subscription;
|
||||||
|
} else {
|
||||||
|
newSubs = new Subscription[1];
|
||||||
|
newSubs[0] = subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiSubs.put(multiClass, newSubs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now subsPerMessageSingle has a unique list of subscriptions for a specific handlerType, and MAY already have subscriptions
|
|
||||||
|
|
||||||
// activates this sub for sub/unsub (only used by the subscription writer thread)
|
// activates this sub for sub/unsub (only used by the subscription writer thread)
|
||||||
subsPerListener.put(listenerClass, subscriptions);
|
subsPerListener.put(listenerClass, subscriptions);
|
||||||
|
|
||||||
|
|
||||||
// add for publication AND subscribe since the data structures are consistent
|
|
||||||
for (int i = 0; i < handlersSize; i++) {
|
|
||||||
subscription = subscriptions[i];
|
|
||||||
subscription.subscribe(listener); // register this callback listener to this subscription
|
|
||||||
|
|
||||||
// THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE!
|
|
||||||
messageHandler = messageHandlers[i];
|
|
||||||
|
|
||||||
// register for publication
|
|
||||||
messageHandlerTypes = messageHandler.getHandledMessages();
|
|
||||||
// final int handlerSize = messageHandlerTypes.length;
|
|
||||||
//
|
|
||||||
// switch (handlerSize) {
|
|
||||||
// case 0: {
|
|
||||||
// handlerType = Void.class;
|
|
||||||
//
|
|
||||||
// // makes this subscription visible for publication
|
|
||||||
// final Subscription[] currentSubs = singleSubs.get(handlerType);
|
|
||||||
// final int currentLength = currentSubs.length;
|
|
||||||
//
|
|
||||||
// // add the new subscription to the array
|
|
||||||
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
|
||||||
// newSubs[currentLength] = subscription;
|
|
||||||
// singleSubs.put(handlerType, newSubs);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// case 1: {
|
|
||||||
handlerType = messageHandlerTypes[0];
|
|
||||||
|
|
||||||
// makes this subscription visible for publication
|
|
||||||
final Subscription[] currentSubs = singleSubs.get(handlerType);
|
|
||||||
final int currentLength = currentSubs.length;
|
|
||||||
|
|
||||||
// add the new subscription to the array
|
|
||||||
final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
|
||||||
newSubs[currentLength] = subscription;
|
|
||||||
singleSubs.put(handlerType, newSubs);
|
|
||||||
|
|
||||||
|
|
||||||
// update the varity/super types
|
|
||||||
// registerExtraSubs(handlerType, singleSubs, localSuperSubs, localVaritySubs);
|
|
||||||
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// case 2: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
|
|
||||||
// messageHandlerTypes[1]);
|
|
||||||
// // makes this subscription visible for publication
|
|
||||||
// final Subscription[] currentSubs = multiSubs.get(multiClass);
|
|
||||||
// final int currentLength = currentSubs.length;
|
|
||||||
//
|
|
||||||
// // add the new subscription to the array
|
|
||||||
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
|
||||||
// newSubs[currentLength] = subscription;
|
|
||||||
// multiSubs.put(multiClass, newSubs);
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// case 3: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes[0],
|
|
||||||
// messageHandlerTypes[1],
|
|
||||||
// messageHandlerTypes[2]);
|
|
||||||
// // makes this subscription visible for publication
|
|
||||||
// final Subscription[] currentSubs = multiSubs.get(multiClass);
|
|
||||||
// final int currentLength = currentSubs.length;
|
|
||||||
//
|
|
||||||
// // add the new subscription to the array
|
|
||||||
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
|
||||||
// newSubs[currentLength] = subscription;
|
|
||||||
// multiSubs.put(multiClass, newSubs);
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// default: {
|
|
||||||
// final MultiClass multiClass = classTree.get(messageHandlerTypes);
|
|
||||||
// // makes this subscription visible for publication
|
|
||||||
// final Subscription[] currentSubs = multiSubs.get(multiClass);
|
|
||||||
// final int currentLength = currentSubs.length;
|
|
||||||
//
|
|
||||||
// // add the new subscription to the array
|
|
||||||
// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class);
|
|
||||||
// newSubs[currentLength] = subscription;
|
|
||||||
// multiSubs.put(multiClass, newSubs);
|
|
||||||
//
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// save this snapshot back to the original (single writer principle)
|
// save this snapshot back to the original (single writer principle)
|
||||||
subsSingleREF.lazySet(this, singleSubs);
|
subsSingleREF.lazySet(this, singleSubs);
|
||||||
// subsMultiREF.lazySet(this, multiSubs);
|
subsMultiREF.lazySet(this, multiSubs);
|
||||||
// subsSuperSingleREF.lazySet(this, localSuperSubs);
|
|
||||||
// subsVaritySingleREF.lazySet(this, localVaritySubs);
|
|
||||||
// subsSuperVaritySingleREF.lazySet(this, localSuperVaritySubs);
|
|
||||||
|
|
||||||
|
|
||||||
// only dump the super subscritions if it is a COMPLETELY NEW subscription. If it's not new, then the heirarchy isn't
|
// only dump the super subscriptions if it is a COMPLETELY NEW subscription.
|
||||||
// changing for super subscriptions
|
// If it's not new, then the hierarchy isn't changing for super subscriptions
|
||||||
subsSuperSingleREF.lazySet(this, new IdentityMap(32));
|
final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
|
||||||
|
localSuperSubs.clear();
|
||||||
|
subsSuperSingleREF.lazySet(this, localSuperSubs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// subscriptions already exist and must only be updated
|
// subscriptions already exist and must only be updated
|
||||||
|
@ -475,7 +407,7 @@ class SubscriptionManager {
|
||||||
// for (int i = 0; i < length; i++) {
|
// for (int i = 0; i < length; i++) {
|
||||||
// sub = arraySubs[i];
|
// sub = arraySubs[i];
|
||||||
//
|
//
|
||||||
// if (sub.getHandler().acceptsVarArgs()) {
|
// if (sub.getHandlerAccess().acceptsVarArgs()) {
|
||||||
// varArgSubsAsList.add(sub);
|
// varArgSubsAsList.add(sub);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -501,7 +433,7 @@ class SubscriptionManager {
|
||||||
// for (int j = 0; j < superSubLength; j++) {
|
// for (int j = 0; j < superSubLength; j++) {
|
||||||
// sub = superSubs[j];
|
// sub = superSubs[j];
|
||||||
//
|
//
|
||||||
// if (sub.getHandler().acceptsSubtypes()) {
|
// if (sub.getHandlerAccess().acceptsSubtypes()) {
|
||||||
// subsAsList.add(sub);
|
// subsAsList.add(sub);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -517,28 +449,6 @@ class SubscriptionManager {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
AtomicBoolean getVarArgPossibility() {
|
|
||||||
return varArgPossibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
VarArgUtils getVarArgUtils() {
|
|
||||||
return varArgUtils;
|
|
||||||
}
|
|
||||||
|
|
||||||
// can return null
|
// can return null
|
||||||
public
|
public
|
||||||
Subscription[] getSubs(final Class<?> messageClass) {
|
Subscription[] getSubs(final Class<?> messageClass) {
|
||||||
|
@ -574,57 +484,54 @@ class SubscriptionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// can NOT return null
|
||||||
|
|
||||||
|
|
||||||
// can return null
|
|
||||||
public
|
public
|
||||||
Subscription[] getSuperSubs(final Class<?> messageClass) {
|
Subscription[] getSuperSubs(final Class<?> messageClass) {
|
||||||
final Class<?>[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response
|
// The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change).
|
||||||
|
// if we subscribe a NEW LISTENER super/child class -- THEN these subscriptions change!
|
||||||
|
// we also DO NOT care about duplicates (since they will be the same anyways)
|
||||||
|
final IdentityMap<Class<?>, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this);
|
||||||
|
|
||||||
final int length = superClasses.length;
|
Subscription[] subscriptions = localSuperSubs.get(messageClass);
|
||||||
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length);
|
// the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are
|
||||||
|
// calculated and do not exist - this will be an empty array.
|
||||||
|
if (subscriptions == null) {
|
||||||
|
final Class<?>[] superClasses = this.classUtils.getSuperClasses(messageClass); // never returns null, cached response
|
||||||
|
|
||||||
final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
|
final int length = superClasses.length;
|
||||||
|
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length);
|
||||||
|
|
||||||
Class<?> superClass;
|
final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
|
||||||
Subscription sub;
|
|
||||||
Subscription[] superSubs;
|
|
||||||
boolean hasSubs = false;
|
|
||||||
|
|
||||||
// walks through all of the subscriptions that might exist for super types, and if applicable, save them
|
Class<?> superClass;
|
||||||
for (int i = 0; i < length; i++) {
|
Subscription sub;
|
||||||
superClass = superClasses[i];
|
Subscription[] superSubs;
|
||||||
superSubs = localSubs.get(superClass);
|
|
||||||
|
|
||||||
if (superSubs != null) {
|
// walks through all of the subscriptions that might exist for super types, and if applicable, save them
|
||||||
int superSubLength = superSubs.length;
|
for (int i = 0; i < length; i++) {
|
||||||
for (int j = 0; j < superSubLength; j++) {
|
superClass = superClasses[i];
|
||||||
sub = superSubs[j];
|
superSubs = localSubs.get(superClass);
|
||||||
|
|
||||||
if (sub.getHandler().acceptsSubtypes()) {
|
if (superSubs != null) {
|
||||||
subsAsList.add(sub);
|
int superSubLength = superSubs.length;
|
||||||
hasSubs = true;
|
for (int j = 0; j < superSubLength; j++) {
|
||||||
|
sub = superSubs[j];
|
||||||
|
|
||||||
|
if (sub.getHandler().acceptsSubtypes()) {
|
||||||
|
subsAsList.add(sub);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subsAsList now contains ALL of the super-class subscriptions.
|
||||||
|
subscriptions = subsAsList.toArray(new Subscription[0]);
|
||||||
|
localSuperSubs.put(messageClass, subscriptions);
|
||||||
|
|
||||||
|
subsSuperSingleREF.lazySet(this, localSuperSubs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return subscriptions;
|
||||||
// subsAsList now contains ALL of the super-class subscriptions.
|
|
||||||
if (hasSubs) {
|
|
||||||
return subsAsList.toArray(new Subscription[0]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO: shortcut out if there are no handlers that accept subtypes
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// IT IS CRITICAL TO REMEMBER: The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change). if we
|
|
||||||
// subscribe a super/child class -- THEN these subscriptions change!
|
|
||||||
|
|
||||||
// return (Subscription[]) subsSuperSingleREF.get(this).get(messageClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// can return null
|
// can return null
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.dispatch;
|
package dorkbox.util.messagebus.subscription.asm;
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
* @author dorkbox, llc
|
* @author dorkbox, llc
|
||||||
* Date: 2/2/15
|
* Date: 2/2/15
|
||||||
*/
|
*/
|
||||||
public interface IHandlerInvocation {
|
public interface AsmInvocation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke the message delivery logic of this handler
|
* Invoke the message delivery logic of this handler
|
||||||
|
@ -88,15 +88,4 @@ public interface IHandlerInvocation {
|
||||||
* @param handler The handler (method) that will be called via reflection
|
* @param handler The handler (method) that will be called via reflection
|
||||||
*/
|
*/
|
||||||
void invoke(Object listener, MethodAccess handler, int methodIndex, Object message1, Object message2, Object message3) throws Throwable;
|
void invoke(Object listener, MethodAccess handler, int methodIndex, Object message1, Object message2, Object message3) throws Throwable;
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the message delivery logic of this handler
|
|
||||||
*
|
|
||||||
* @param listener The listener that will receive the message. This can be a reference to a method object
|
|
||||||
* from the java reflection api or any other wrapper that can be used to invoke the handler
|
|
||||||
* @param messages The message to be delivered to the handler. This can be any object compatible with the object
|
|
||||||
* type that the handler consumes
|
|
||||||
* @param handler The handler (method) that will be called via reflection
|
|
||||||
*/
|
|
||||||
void invoke(Object listener, MethodAccess handler, int methodIndex, Object... messages) throws Throwable;
|
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.dispatch;
|
package dorkbox.util.messagebus.subscription.asm;
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
|
||||||
|
@ -48,10 +48,10 @@ import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
* Date: 2/2/15
|
* Date: 2/2/15
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class ReflectiveHandlerInvocation implements IHandlerInvocation {
|
class AsmReflectiveInvocation implements AsmInvocation {
|
||||||
|
|
||||||
public
|
public
|
||||||
ReflectiveHandlerInvocation() {
|
AsmReflectiveInvocation() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,21 +63,13 @@ class ReflectiveHandlerInvocation implements IHandlerInvocation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2)
|
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2) throws Throwable {
|
||||||
throws Throwable {
|
|
||||||
handler.invoke(listener, methodIndex, message1, message2);
|
handler.invoke(listener, methodIndex, message1, message2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2,
|
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
final Object message3) throws Throwable {
|
|
||||||
handler.invoke(listener, methodIndex, message1, message2, message3);
|
handler.invoke(listener, methodIndex, message1, message2, message3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable {
|
|
||||||
handler.invoke(listener, methodIndex, messages);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -35,7 +35,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.dispatch;
|
package dorkbox.util.messagebus.subscription.asm;
|
||||||
|
|
||||||
import com.esotericsoftware.reflectasm.MethodAccess;
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
|
||||||
|
@ -48,12 +48,12 @@ import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
* Date: 2/2/15
|
* Date: 2/2/15
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class SynchronizedHandlerInvocation implements IHandlerInvocation {
|
class AsmSynchronizedInvocation implements AsmInvocation {
|
||||||
|
|
||||||
private IHandlerInvocation delegate;
|
private AsmInvocation delegate;
|
||||||
|
|
||||||
public
|
public
|
||||||
SynchronizedHandlerInvocation(IHandlerInvocation delegate) {
|
AsmSynchronizedInvocation(AsmInvocation delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +67,7 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2)
|
void invoke(final Object listener, final MethodAccess handler, int methodIndex, final Object message1, final Object message2) throws Throwable {
|
||||||
throws Throwable {
|
|
||||||
synchronized (listener) {
|
synchronized (listener) {
|
||||||
this.delegate.invoke(listener, handler, methodIndex, message1, message2);
|
this.delegate.invoke(listener, handler, methodIndex, message1, message2);
|
||||||
}
|
}
|
||||||
|
@ -76,18 +75,9 @@ class SynchronizedHandlerInvocation implements IHandlerInvocation {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object message1, final Object message2,
|
void invoke(final Object listener, final MethodAccess handler, final int methodIndex, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
final Object message3) throws Throwable {
|
|
||||||
synchronized (listener) {
|
synchronized (listener) {
|
||||||
this.delegate.invoke(listener, handler, methodIndex, message1, message2, message3);
|
this.delegate.invoke(listener, handler, methodIndex, message1, message2, message3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void invoke(final Object listener, MethodAccess handler, int methodIndex, final Object... messages) throws Throwable {
|
|
||||||
synchronized (listener) {
|
|
||||||
this.delegate.invoke(listener, handler, methodIndex, messages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.asm;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubMaker;
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class SubMakerAsm implements SubMaker {
|
||||||
|
|
||||||
|
public
|
||||||
|
SubMakerAsm() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Subscription create(final Class<?> listenerClass, final MessageHandler handler) {
|
||||||
|
return new SubscriptionAsm(listenerClass, handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Benjamin Diedrichsen
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.asm;
|
||||||
|
|
||||||
|
import com.esotericsoftware.reflectasm.MethodAccess;
|
||||||
|
import dorkbox.util.messagebus.common.Entry;
|
||||||
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subscription is a container that manages exactly one message handler of all registered
|
||||||
|
* message listeners of the same class, i.e. all subscribed instances (excluding subclasses) of a message
|
||||||
|
* will be referenced in the subscription created for a message.
|
||||||
|
* <p/>
|
||||||
|
* There will be as many unique subscription objects per message listener class as there are message handlers
|
||||||
|
* defined in the message listeners class hierarchy.
|
||||||
|
* <p/>
|
||||||
|
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
|
||||||
|
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
|
||||||
|
*
|
||||||
|
* @author bennidi
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public final
|
||||||
|
class SubscriptionAsm extends Subscription {
|
||||||
|
private final AsmInvocation invocation;
|
||||||
|
|
||||||
|
private final MethodAccess handlerAccess;
|
||||||
|
private final int methodIndex;
|
||||||
|
|
||||||
|
public
|
||||||
|
SubscriptionAsm(final Class<?> listenerClass, final MessageHandler handler) {
|
||||||
|
// we use ASM here
|
||||||
|
super(listenerClass, handler);
|
||||||
|
|
||||||
|
AsmInvocation invocation = new AsmReflectiveInvocation();
|
||||||
|
if (handler.isSynchronized()) {
|
||||||
|
invocation = new AsmSynchronizedInvocation(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.invocation = invocation;
|
||||||
|
|
||||||
|
// we use ASM here
|
||||||
|
Method method = handler.getMethod();
|
||||||
|
this.handlerAccess = MethodAccess.get(method.getDeclaringClass());
|
||||||
|
this.methodIndex = this.handlerAccess.getIndex(method.getName(), handler.getHandledMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message) throws Throwable {
|
||||||
|
final MethodAccess handler = this.handlerAccess;
|
||||||
|
final int handleIndex = this.methodIndex;
|
||||||
|
final AsmInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, handler, handleIndex, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message1, final Object message2) throws Throwable {
|
||||||
|
final MethodAccess handler = this.handlerAccess;
|
||||||
|
final int handleIndex = this.methodIndex;
|
||||||
|
final AsmInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, handler, handleIndex, message1, message2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
final MethodAccess handler = this.handlerAccess;
|
||||||
|
final int handleIndex = this.methodIndex;
|
||||||
|
final AsmInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, handler, handleIndex, message1, message2, message3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Benjamin Diedrichsen
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.reflection;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler invocation encapsulates the logic that is used to invoke a single
|
||||||
|
* message handler to process a given message.
|
||||||
|
*
|
||||||
|
* A handler invocation might come in different flavours and can be composed
|
||||||
|
* of various independent invocations by means of delegation (-> decorator pattern)
|
||||||
|
*
|
||||||
|
* If an exception is thrown during handler invocation it is wrapped and propagated
|
||||||
|
* as a publication error
|
||||||
|
*
|
||||||
|
* @author bennidi
|
||||||
|
* Date: 11/23/12
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public interface ReflectionInvocation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the message delivery logic of this handler
|
||||||
|
*
|
||||||
|
* @param listener The listener that will receive the message. This can be a reference to a method object
|
||||||
|
* from the java reflection api or any other wrapper that can be used to invoke the handler
|
||||||
|
* @param message The message to be delivered to the handler. This can be any object compatible with the object
|
||||||
|
* type that the handler consumes
|
||||||
|
* @param handler The handler (method) that will be called via reflection
|
||||||
|
*/
|
||||||
|
void invoke(Object listener, Method handler, Object message) throws Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the message delivery logic of this handler
|
||||||
|
*
|
||||||
|
* @param listener The listener that will receive the message. This can be a reference to a method object
|
||||||
|
* from the java reflection api or any other wrapper that can be used to invoke the handler
|
||||||
|
* @param message1 The message to be delivered to the handler. This can be any object compatible with the object
|
||||||
|
* type that the handler consumes
|
||||||
|
* @param handler The handler (method) that will be called via reflection
|
||||||
|
*/
|
||||||
|
void invoke(Object listener, Method handler, Object message1, Object message2) throws Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the message delivery logic of this handler
|
||||||
|
*
|
||||||
|
* @param listener The listener that will receive the message. This can be a reference to a method object
|
||||||
|
* from the java reflection api or any other wrapper that can be used to invoke the handler
|
||||||
|
* @param message1 The message to be delivered to the handler. This can be any object compatible with the object
|
||||||
|
* type that the handler consumes
|
||||||
|
* @param handler The handler (method) that will be called via reflection
|
||||||
|
*/
|
||||||
|
void invoke(Object listener, Method handler, Object message1, Object message2, Object message3) throws Throwable;
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Benjamin Diedrichsen
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.reflection;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses reflection to invoke a message handler for a given message.
|
||||||
|
*
|
||||||
|
* @author bennidi
|
||||||
|
* Date: 11/23/12
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class ReflectionReflectiveInvocation implements ReflectionInvocation {
|
||||||
|
|
||||||
|
public
|
||||||
|
ReflectionReflectiveInvocation() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, final Method handler, final Object message) throws Throwable {
|
||||||
|
handler.invoke(listener, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, final Method handler, final Object message1, final Object message2) throws Throwable {
|
||||||
|
handler.invoke(listener, message1, message2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, final Method handler, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
handler.invoke(listener, message1, message2, message3);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Benjamin Diedrichsen
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.reflection;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes message handler invocations for all handlers that specify @Synchronized
|
||||||
|
*
|
||||||
|
* @author bennidi
|
||||||
|
* Date: 3/31/13
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class ReflectionSynchronizedInvocation implements ReflectionInvocation {
|
||||||
|
|
||||||
|
private ReflectionInvocation delegate;
|
||||||
|
|
||||||
|
public
|
||||||
|
ReflectionSynchronizedInvocation(ReflectionInvocation delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, final Method handler, final Object message) throws Throwable {
|
||||||
|
synchronized (listener) {
|
||||||
|
this.delegate.invoke(listener, handler, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, Method handler, final Object message1, final Object message2) throws Throwable {
|
||||||
|
synchronized (listener) {
|
||||||
|
this.delegate.invoke(listener, handler, message1, message2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void invoke(final Object listener, final Method handler, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
synchronized (listener) {
|
||||||
|
this.delegate.invoke(listener, handler, message1, message2, message3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.reflection;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
|
import dorkbox.util.messagebus.subscription.SubMaker;
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
|
public
|
||||||
|
class SubMakerReflection implements SubMaker {
|
||||||
|
|
||||||
|
public
|
||||||
|
SubMakerReflection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Subscription create(final Class<?> listenerClass, final MessageHandler handler) {
|
||||||
|
return new SubscriptionReflection(listenerClass, handler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Benjamin Diedrichsen
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.subscription.reflection;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.Entry;
|
||||||
|
import dorkbox.util.messagebus.common.MessageHandler;
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A subscription is a container that manages exactly one message handler of all registered
|
||||||
|
* message listeners of the same class, i.e. all subscribed instances (excluding subclasses) of a message
|
||||||
|
* will be referenced in the subscription created for a message.
|
||||||
|
* <p/>
|
||||||
|
* There will be as many unique subscription objects per message listener class as there are message handlers
|
||||||
|
* defined in the message listeners class hierarchy.
|
||||||
|
* <p/>
|
||||||
|
* This class uses the "single writer principle", so that the subscription are only MODIFIED by a single thread,
|
||||||
|
* but are READ by X number of threads (in a safe way). This uses object thread visibility/publication to work.
|
||||||
|
*
|
||||||
|
* @author bennidi
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public final
|
||||||
|
class SubscriptionReflection extends Subscription {
|
||||||
|
private final Method method;
|
||||||
|
private final ReflectionInvocation invocation;
|
||||||
|
|
||||||
|
public
|
||||||
|
SubscriptionReflection(final Class<?> listenerClass, final MessageHandler handler) {
|
||||||
|
// we use "normal java" here
|
||||||
|
super(listenerClass, handler);
|
||||||
|
|
||||||
|
ReflectionInvocation invocation = new ReflectionReflectiveInvocation();
|
||||||
|
if (handler.isSynchronized()) {
|
||||||
|
invocation = new ReflectionSynchronizedInvocation(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.invocation = invocation;
|
||||||
|
method = handler.getMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message) throws Throwable {
|
||||||
|
final Method method = this.method;
|
||||||
|
final ReflectionInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, method, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message1, final Object message2) throws Throwable {
|
||||||
|
final Method method = this.method;
|
||||||
|
final ReflectionInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, method, message1, message2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if messages were published
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
boolean publish(final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
final Method method = this.method;
|
||||||
|
final ReflectionInvocation invocation = this.invocation;
|
||||||
|
boolean hasSubs = false;
|
||||||
|
|
||||||
|
Entry current = headREF.get(this);
|
||||||
|
Object listener;
|
||||||
|
while (current != null) {
|
||||||
|
hasSubs = true;
|
||||||
|
listener = current.getValue();
|
||||||
|
current = current.next();
|
||||||
|
|
||||||
|
invocation.invoke(listener, method, message1, message2, message3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSubs;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
package dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
|
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
|
import dorkbox.util.messagebus.error.PublicationError;
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
import dorkbox.util.messagebus.publication.Publisher;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This is similar to the disruptor, however the downside of this implementation is that, while faster than the no-gc version, it
|
||||||
|
* generates garbage (while the disruptor version does not).
|
||||||
*
|
*
|
||||||
|
* Basically, the disruptor is fast + noGC.
|
||||||
|
*
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class AsyncABQ implements Synchrony {
|
class AsyncABQ implements Synchrony {
|
||||||
|
|
||||||
private final ErrorHandlingSupport errorHandler;
|
private final ArrayBlockingQueue<MessageHolder> dispatchQueue;
|
||||||
private final ArrayBlockingQueue<Object> dispatchQueue;
|
|
||||||
private final Collection<Thread> threads;
|
private final Collection<Thread> threads;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,8 +52,7 @@ class AsyncABQ implements Synchrony {
|
||||||
final Publisher publisher,
|
final Publisher publisher,
|
||||||
final Synchrony syncPublication) {
|
final Synchrony syncPublication) {
|
||||||
|
|
||||||
this.errorHandler = errorHandler;
|
this.dispatchQueue = new ArrayBlockingQueue<MessageHolder>(1024);
|
||||||
this.dispatchQueue = new ArrayBlockingQueue<Object>(1024);
|
|
||||||
|
|
||||||
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
||||||
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
|
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
|
||||||
|
@ -43,52 +63,79 @@ class AsyncABQ implements Synchrony {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
final ArrayBlockingQueue<?> IN_QUEUE = AsyncABQ.this.dispatchQueue;
|
final ArrayBlockingQueue<MessageHolder> IN_QUEUE = AsyncABQ.this.dispatchQueue;
|
||||||
final Publisher publisher1 = publisher;
|
final Publisher publisher1 = publisher;
|
||||||
final Synchrony syncPublication1 = syncPublication;
|
final Synchrony syncPublication1 = syncPublication;
|
||||||
|
final ErrorHandlingSupport errorHandler1 = errorHandler;
|
||||||
|
|
||||||
|
MessageHolder event = null;
|
||||||
|
int messageType = MessageType.ONE;
|
||||||
|
Object message1 = null;
|
||||||
|
Object message2 = null;
|
||||||
|
Object message3 = null;
|
||||||
|
|
||||||
while (!AsyncABQ.this.shuttingDown) {
|
while (!AsyncABQ.this.shuttingDown) {
|
||||||
try {
|
try {
|
||||||
//noinspection InfiniteLoopStatement
|
event = IN_QUEUE.take();
|
||||||
while (true) {
|
messageType = event.type;
|
||||||
final Object take = IN_QUEUE.take();
|
message1 = event.message1;
|
||||||
publisher1.publish(syncPublication1, take);
|
message2 = event.message2;
|
||||||
|
message3 = event.message3;
|
||||||
|
|
||||||
|
switch (messageType) {
|
||||||
|
case MessageType.ONE: {
|
||||||
|
publisher1.publish(syncPublication1, message1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.TWO: {
|
||||||
|
publisher1.publish(syncPublication1, message1, message2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.THREE: {
|
||||||
|
publisher1.publish(syncPublication1, message3, message1, message2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (!AsyncABQ.this.shuttingDown) {
|
if (!AsyncABQ.this.shuttingDown) {
|
||||||
// Integer type = (Integer) MultiNode.lpMessageType(node);
|
switch (messageType) {
|
||||||
// switch (type) {
|
case MessageType.ONE: {
|
||||||
// case 1: {
|
PublicationError publicationError = new PublicationError()
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
.setMessage("Thread interrupted while processing message")
|
||||||
// "Thread interrupted while processing message")
|
.setCause(e);
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
if (event != null) {
|
||||||
// break;
|
publicationError.setPublishedObject(message1);
|
||||||
// }
|
}
|
||||||
// case 2: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
// "Thread interrupted while processing message")
|
break;
|
||||||
// .setCause(e)
|
}
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
case MessageType.TWO: {
|
||||||
// MultiNode.lpItem2(node)));
|
PublicationError publicationError = new PublicationError()
|
||||||
// break;
|
.setMessage("Thread interrupted while processing message")
|
||||||
// }
|
.setCause(e);
|
||||||
// case 3: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
if (event != null) {
|
||||||
// "Thread interrupted while processing message")
|
publicationError.setPublishedObject(message1, message2);
|
||||||
// .setCause(e)
|
}
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
|
||||||
// MultiNode.lpItem2(node),
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
// MultiNode.lpItem3(node)));
|
break;
|
||||||
// break;
|
}
|
||||||
// }
|
case MessageType.THREE: {
|
||||||
// default: {
|
PublicationError publicationError = new PublicationError()
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
.setMessage("Thread interrupted while processing message")
|
||||||
// "Thread interrupted while processing message")
|
.setCause(e);
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
if (event != null) {
|
||||||
// }
|
publicationError.setPublishedObject(message1, message2, message3);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,27 +147,28 @@ class AsyncABQ implements Synchrony {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unfortunately, this isn't as friendly to GC as the disruptor is...
|
||||||
|
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
||||||
this.dispatchQueue.put(message1);
|
MessageHolder take = new MessageHolder();
|
||||||
|
take.type = MessageType.ONE;
|
||||||
|
take.subscriptions = subscriptions;
|
||||||
|
take.message1 = message1;
|
||||||
|
|
||||||
|
this.dispatchQueue.put(take);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
||||||
|
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2, message3));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
213
src/dorkbox/util/messagebus/synchrony/AsyncABQ_noGc.java
Normal file
213
src/dorkbox/util/messagebus/synchrony/AsyncABQ_noGc.java
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
||||||
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
|
import dorkbox.util.messagebus.error.PublicationError;
|
||||||
|
import dorkbox.util.messagebus.publication.Publisher;
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is similar in behavior to the disruptor in that it does not generate garbage, however the downside of this implementation is it is
|
||||||
|
* slow, but faster than other messagebus implementations.
|
||||||
|
*
|
||||||
|
* Basically, the disruptor is fast + noGC.
|
||||||
|
*
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class AsyncABQ_noGc implements Synchrony {
|
||||||
|
|
||||||
|
private final ArrayBlockingQueue<MessageHolder> dispatchQueue;
|
||||||
|
|
||||||
|
// have two queues to prevent garbage, So we pull off one queue to add to another queue and when done, we put it back
|
||||||
|
private final ArrayBlockingQueue<MessageHolder> gcQueue;
|
||||||
|
|
||||||
|
|
||||||
|
private final Collection<Thread> threads;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the consumers during shutdown, that it's on purpose.
|
||||||
|
*/
|
||||||
|
private volatile boolean shuttingDown = false;
|
||||||
|
|
||||||
|
|
||||||
|
public
|
||||||
|
AsyncABQ_noGc(final int numberOfThreads,
|
||||||
|
final ErrorHandlingSupport errorHandler,
|
||||||
|
final Publisher publisher,
|
||||||
|
final Synchrony syncPublication) {
|
||||||
|
|
||||||
|
this.dispatchQueue = new ArrayBlockingQueue<MessageHolder>(1024);
|
||||||
|
this.gcQueue = new ArrayBlockingQueue<MessageHolder>(1024);
|
||||||
|
|
||||||
|
// this is how we prevent garbage
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
gcQueue.add(new MessageHolder());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
||||||
|
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
|
||||||
|
for (int i = 0; i < numberOfThreads; i++) {
|
||||||
|
|
||||||
|
// each thread will run forever and process incoming message publication requests
|
||||||
|
Runnable runnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
final ArrayBlockingQueue<MessageHolder> IN_QUEUE = AsyncABQ_noGc.this.dispatchQueue;
|
||||||
|
final ArrayBlockingQueue<MessageHolder> OUT_QUEUE = AsyncABQ_noGc.this.gcQueue;
|
||||||
|
final Publisher publisher1 = publisher;
|
||||||
|
final Synchrony syncPublication1 = syncPublication;
|
||||||
|
final ErrorHandlingSupport errorHandler1 = errorHandler;
|
||||||
|
|
||||||
|
MessageHolder event = null;
|
||||||
|
int messageType = MessageType.ONE;
|
||||||
|
Object message1 = null;
|
||||||
|
Object message2 = null;
|
||||||
|
Object message3 = null;
|
||||||
|
|
||||||
|
while (!AsyncABQ_noGc.this.shuttingDown) {
|
||||||
|
try {
|
||||||
|
event = IN_QUEUE.take();
|
||||||
|
messageType = event.type;
|
||||||
|
message1 = event.message1;
|
||||||
|
message2 = event.message2;
|
||||||
|
message3 = event.message3;
|
||||||
|
|
||||||
|
OUT_QUEUE.put(event);
|
||||||
|
|
||||||
|
|
||||||
|
switch (messageType) {
|
||||||
|
case MessageType.ONE: {
|
||||||
|
publisher1.publish(syncPublication1, message1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.TWO: {
|
||||||
|
publisher1.publish(syncPublication1, message1, message2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.THREE: {
|
||||||
|
publisher1.publish(syncPublication1, message3, message1, message2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (!AsyncABQ_noGc.this.shuttingDown) {
|
||||||
|
switch (messageType) {
|
||||||
|
case MessageType.ONE: {
|
||||||
|
PublicationError publicationError = new PublicationError()
|
||||||
|
.setMessage("Thread interrupted while processing message")
|
||||||
|
.setCause(e);
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
publicationError.setPublishedObject(message1);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.TWO: {
|
||||||
|
PublicationError publicationError = new PublicationError()
|
||||||
|
.setMessage("Thread interrupted while processing message")
|
||||||
|
.setCause(e);
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
publicationError.setPublishedObject(message1, message2);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageType.THREE: {
|
||||||
|
PublicationError publicationError = new PublicationError()
|
||||||
|
.setMessage("Thread interrupted while processing message")
|
||||||
|
.setCause(e);
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
publicationError.setPublishedObject(message1, message2, message3);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler1.handlePublicationError(publicationError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Thread runner = threadFactory.newThread(runnable);
|
||||||
|
this.threads.add(runner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfortunately, this isn't as friendly to GC as the disruptor is...
|
||||||
|
|
||||||
|
public
|
||||||
|
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
||||||
|
MessageHolder take = gcQueue.take();
|
||||||
|
take.type = MessageType.ONE;
|
||||||
|
take.subscriptions = subscriptions;
|
||||||
|
take.message1 = message1;
|
||||||
|
|
||||||
|
this.dispatchQueue.put(take);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
||||||
|
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
this.dispatchQueue.put(new MessageHolder(subscriptions, message1, message2, message3));
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void start() {
|
||||||
|
if (shuttingDown) {
|
||||||
|
throw new Error("Unable to restart the MessageBus");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Thread t : this.threads) {
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void shutdown() {
|
||||||
|
this.shuttingDown = true;
|
||||||
|
|
||||||
|
for (Thread t : this.threads) {
|
||||||
|
t.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
boolean hasPendingMessages() {
|
||||||
|
return !this.dispatchQueue.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
package dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
import com.lmax.disruptor.LiteBlockingWaitStrategy;
|
import com.lmax.disruptor.LiteBlockingWaitStrategy;
|
||||||
|
@ -8,14 +23,13 @@ import com.lmax.disruptor.SequenceBarrier;
|
||||||
import com.lmax.disruptor.Sequencer;
|
import com.lmax.disruptor.Sequencer;
|
||||||
import com.lmax.disruptor.WaitStrategy;
|
import com.lmax.disruptor.WaitStrategy;
|
||||||
import com.lmax.disruptor.WorkProcessor;
|
import com.lmax.disruptor.WorkProcessor;
|
||||||
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
|
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
import dorkbox.util.messagebus.publication.Publisher;
|
||||||
import dorkbox.util.messagebus.publication.disruptor.EventBusFactory;
|
import dorkbox.util.messagebus.synchrony.disruptor.EventBusFactory;
|
||||||
import dorkbox.util.messagebus.publication.disruptor.MessageHandler;
|
import dorkbox.util.messagebus.synchrony.disruptor.MessageHandler;
|
||||||
import dorkbox.util.messagebus.publication.disruptor.MessageHolder;
|
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
|
||||||
import dorkbox.util.messagebus.publication.disruptor.MessageType;
|
import dorkbox.util.messagebus.synchrony.disruptor.PublicationExceptionHandler;
|
||||||
import dorkbox.util.messagebus.publication.disruptor.PublicationExceptionHandler;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -23,6 +37,9 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.LockSupport;
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
|
*/
|
||||||
public
|
public
|
||||||
class AsyncDisruptor implements Synchrony {
|
class AsyncDisruptor implements Synchrony {
|
||||||
|
|
||||||
|
@ -130,12 +147,6 @@ class AsyncDisruptor implements Synchrony {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets the sequences used for processing work
|
// gets the sequences used for processing work
|
||||||
private
|
private
|
||||||
Sequence[] getSequences() {
|
Sequence[] getSequences() {
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
package dorkbox.util.messagebus.synchrony;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
|
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class AsyncLTQ implements Synchrony {
|
|
||||||
private final ErrorHandlingSupport errorHandler;
|
|
||||||
private final LinkedTransferQueue<Object> dispatchQueue;
|
|
||||||
private final Collection<Thread> threads;
|
|
||||||
/**
|
|
||||||
* Notifies the consumers during shutdown, that it's on purpose.
|
|
||||||
*/
|
|
||||||
private volatile boolean shuttingDown = false;
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
AsyncLTQ(final int numberOfThreads,
|
|
||||||
final ErrorHandlingSupport errorHandler,
|
|
||||||
final Publisher publisher,
|
|
||||||
final Synchrony syncPublication) {
|
|
||||||
|
|
||||||
this.errorHandler = errorHandler;
|
|
||||||
this.dispatchQueue = new LinkedTransferQueue<Object>();
|
|
||||||
|
|
||||||
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
|
||||||
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
|
|
||||||
for (int i = 0; i < numberOfThreads; i++) {
|
|
||||||
|
|
||||||
// each thread will run forever and process incoming message publication requests
|
|
||||||
Runnable runnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
LinkedTransferQueue<?> IN_QUEUE = AsyncLTQ.this.dispatchQueue;
|
|
||||||
final Publisher publisher1 = publisher;
|
|
||||||
final Synchrony syncPublication1 = syncPublication;
|
|
||||||
|
|
||||||
while (!AsyncLTQ.this.shuttingDown) {
|
|
||||||
try {
|
|
||||||
//noinspection InfiniteLoopStatement
|
|
||||||
while (true) {
|
|
||||||
final Object take = IN_QUEUE.take();
|
|
||||||
publisher1.publish(syncPublication1, take);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if (!AsyncLTQ.this.shuttingDown) {
|
|
||||||
// Integer type = (Integer) MultiNode.lpMessageType(node);
|
|
||||||
// switch (type) {
|
|
||||||
// case 1: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 2: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
|
||||||
// MultiNode.lpItem2(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 3: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
|
||||||
// MultiNode.lpItem2(node),
|
|
||||||
// MultiNode.lpItem3(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// default: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Thread runner = threadFactory.newThread(runnable);
|
|
||||||
this.threads.add(runner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
|
||||||
this.dispatchQueue.transfer(message1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void start() {
|
|
||||||
if (shuttingDown) {
|
|
||||||
throw new Error("Unable to restart the MessageBus");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Thread t : this.threads) {
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void shutdown() {
|
|
||||||
this.shuttingDown = true;
|
|
||||||
|
|
||||||
for (Thread t : this.threads) {
|
|
||||||
t.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
boolean hasPendingMessages() {
|
|
||||||
return !this.dispatchQueue.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
package dorkbox.util.messagebus.synchrony;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.thread.NamedThreadFactory;
|
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class AsyncMisc implements Synchrony {
|
|
||||||
/**
|
|
||||||
* Notifies the consumers during shutdown, that it's on purpose.
|
|
||||||
*/
|
|
||||||
private volatile boolean shuttingDown = false;
|
|
||||||
|
|
||||||
|
|
||||||
// private final LinkedBlockingQueue<Object> dispatchQueue;
|
|
||||||
// private final ArrayBlockingQueue<Object> dispatchQueue;
|
|
||||||
private final LinkedTransferQueue<Object> dispatchQueue;
|
|
||||||
private final Collection<Thread> threads;
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
AsyncMisc(final int numberOfThreads,
|
|
||||||
final ErrorHandlingSupport errorHandler,
|
|
||||||
final Publisher publisher,
|
|
||||||
final Synchrony syncPublication) {
|
|
||||||
|
|
||||||
// this.dispatchQueue = new LinkedBlockingQueue<Object>(1024);
|
|
||||||
// this.dispatchQueue = new ArrayBlockingQueue<Object>(1024);
|
|
||||||
this.dispatchQueue = new LinkedTransferQueue<Object>();
|
|
||||||
|
|
||||||
this.threads = new ArrayDeque<Thread>(numberOfThreads);
|
|
||||||
final NamedThreadFactory threadFactory = new NamedThreadFactory("MessageBus");
|
|
||||||
for (int i = 0; i < numberOfThreads; i++) {
|
|
||||||
|
|
||||||
// each thread will run forever and process incoming message publication requests
|
|
||||||
Runnable runnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// LinkedBlockingQueue<?> IN_QUEUE = MessageBus.this.dispatchQueue;
|
|
||||||
// ArrayBlockingQueue<?> IN_QUEUE = MessageBus.this.dispatchQueue;
|
|
||||||
LinkedTransferQueue<?> IN_QUEUE = AsyncMisc.this.dispatchQueue;
|
|
||||||
|
|
||||||
// MultiNode node = new MultiNode();
|
|
||||||
while (!AsyncMisc.this.shuttingDown) {
|
|
||||||
try {
|
|
||||||
//noinspection InfiniteLoopStatement
|
|
||||||
while (true) {
|
|
||||||
// IN_QUEUE.take(node);
|
|
||||||
final Object take = IN_QUEUE.take();
|
|
||||||
// Integer type = (Integer) MultiNode.lpMessageType(node);
|
|
||||||
// switch (type) {
|
|
||||||
// case 1: {
|
|
||||||
// publish(take);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 2: {
|
|
||||||
// publish(MultiNode.lpItem1(node), MultiNode.lpItem2(node));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 3: {
|
|
||||||
// publish(MultiNode.lpItem1(node), MultiNode.lpItem2(node), MultiNode.lpItem3(node));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// default: {
|
|
||||||
// publish(MultiNode.lpItem1(node));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if (!AsyncMisc.this.shuttingDown) {
|
|
||||||
// Integer type = (Integer) MultiNode.lpMessageType(node);
|
|
||||||
// switch (type) {
|
|
||||||
// case 1: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 2: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
|
||||||
// MultiNode.lpItem2(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case 3: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node),
|
|
||||||
// MultiNode.lpItem2(node),
|
|
||||||
// MultiNode.lpItem3(node)));
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// default: {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Thread interrupted while processing message")
|
|
||||||
// .setCause(e)
|
|
||||||
// .setPublishedObject(MultiNode.lpItem1(node)));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Thread runner = threadFactory.newThread(runnable);
|
|
||||||
this.threads.add(runner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// this.dispatchQueue.transfer(message);
|
|
||||||
//// this.dispatchQueue.put(message);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// errorHandler.handlePublicationError(new PublicationError().setMessage(
|
|
||||||
// "Error while adding an asynchronous message").setCause(e).setPublishedObject(message));
|
|
||||||
// }
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
|
||||||
sub = subscriptions[i];
|
|
||||||
sub.publish(message1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void start() {
|
|
||||||
if (shuttingDown) {
|
|
||||||
throw new Error("Unable to restart the MessageBus");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Thread t : this.threads) {
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void shutdown() {
|
|
||||||
this.shuttingDown = true;
|
|
||||||
|
|
||||||
for (Thread t : this.threads) {
|
|
||||||
t.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
boolean hasPendingMessages() {
|
|
||||||
return !this.dispatchQueue.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
60
src/dorkbox/util/messagebus/synchrony/MessageHolder.java
Normal file
60
src/dorkbox/util/messagebus/synchrony/MessageHolder.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
import dorkbox.util.messagebus.synchrony.disruptor.MessageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class MessageHolder {
|
||||||
|
public int type = MessageType.ONE;
|
||||||
|
public Subscription[] subscriptions;
|
||||||
|
|
||||||
|
public Object message1 = null;
|
||||||
|
public Object message2 = null;
|
||||||
|
public Object message3 = null;
|
||||||
|
|
||||||
|
public
|
||||||
|
MessageHolder() {}
|
||||||
|
|
||||||
|
public
|
||||||
|
MessageHolder(Subscription[] subscriptions, Object message1) {
|
||||||
|
this.subscriptions = subscriptions;
|
||||||
|
this.message1 = message1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
MessageHolder(Subscription[] subscriptions, Object message1, Object message2) {
|
||||||
|
type = MessageType.TWO;
|
||||||
|
|
||||||
|
this.subscriptions = subscriptions;
|
||||||
|
this.message1 = message1;
|
||||||
|
this.message2 = message2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
MessageHolder(Subscription[] subscriptions, Object message1, Object message2, Object message3) {
|
||||||
|
type = MessageType.THREE;
|
||||||
|
|
||||||
|
this.subscriptions = subscriptions;
|
||||||
|
this.message1 = message1;
|
||||||
|
this.message2 = message2;
|
||||||
|
this.message3 = message3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
package dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/2/15
|
||||||
|
*/
|
||||||
public
|
public
|
||||||
class Sync implements Synchrony {
|
class Sync implements Synchrony {
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1) throws Throwable {
|
||||||
Subscription sub;
|
Subscription sub;
|
||||||
|
boolean hasSubs = false;
|
||||||
for (int i = 0; i < subscriptions.length; i++) {
|
for (int i = 0; i < subscriptions.length; i++) {
|
||||||
sub = subscriptions[i];
|
sub = subscriptions[i];
|
||||||
sub.publish(message1);
|
sub.publish(message1);
|
||||||
|
@ -16,19 +35,21 @@ class Sync implements Synchrony {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2) throws Throwable {
|
||||||
|
Subscription sub;
|
||||||
|
for (int i = 0; i < subscriptions.length; i++) {
|
||||||
|
sub = subscriptions[i];
|
||||||
|
sub.publish(message1, message2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
void publish(final Subscription[] subscriptions, final Object message1, final Object message2, final Object message3) throws Throwable {
|
||||||
|
Subscription sub;
|
||||||
}
|
for (int i = 0; i < subscriptions.length; i++) {
|
||||||
|
sub = subscriptions[i];
|
||||||
@Override
|
sub.publish(message1, message2, message3);
|
||||||
public
|
}
|
||||||
void publish(final Subscription[] subscriptions, final Object[] messages) throws Throwable {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony;
|
package dorkbox.util.messagebus.synchrony;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
interface Synchrony {
|
interface Synchrony {
|
||||||
void publish(final Subscription[] subscriptions, Object message1) throws Throwable;
|
void publish(final Subscription[] subscriptions, Object message1) throws Throwable;
|
||||||
void publish(final Subscription[] subscriptions, Object message1, Object message2) throws Throwable ;
|
void publish(final Subscription[] subscriptions, Object message1, Object message2) throws Throwable ;
|
||||||
void publish(final Subscription[] subscriptions, Object message1, Object message2, Object message3) throws Throwable ;
|
void publish(final Subscription[] subscriptions, Object message1, Object message2, Object message3) throws Throwable ;
|
||||||
void publish(final Subscription[] subscriptions, Object[] messages) throws Throwable ;
|
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony.disruptor;
|
||||||
|
|
||||||
|
import com.lmax.disruptor.EventFactory;
|
||||||
|
import dorkbox.util.messagebus.synchrony.MessageHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc
|
||||||
|
* Date: 2/2/15
|
||||||
|
*/
|
||||||
|
public class EventBusFactory implements EventFactory<MessageHolder> {
|
||||||
|
|
||||||
|
public EventBusFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
MessageHolder newInstance() {
|
||||||
|
return new MessageHolder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,23 @@
|
||||||
package dorkbox.util.messagebus.publication.disruptor;
|
/*
|
||||||
|
* Copyright 2015 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony.disruptor;
|
||||||
|
|
||||||
import com.lmax.disruptor.LifecycleAware;
|
import com.lmax.disruptor.LifecycleAware;
|
||||||
import com.lmax.disruptor.WorkHandler;
|
import com.lmax.disruptor.WorkHandler;
|
||||||
|
import dorkbox.util.messagebus.synchrony.MessageHolder;
|
||||||
import dorkbox.util.messagebus.synchrony.Synchrony;
|
import dorkbox.util.messagebus.synchrony.Synchrony;
|
||||||
import dorkbox.util.messagebus.publication.Publisher;
|
import dorkbox.util.messagebus.publication.Publisher;
|
||||||
|
|
||||||
|
@ -47,11 +63,6 @@ class MessageHandler implements WorkHandler<MessageHolder>, LifecycleAware {
|
||||||
this.publisher.publish(syncPublication, message3, message1, message2);
|
this.publisher.publish(syncPublication, message3, message1, message2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case MessageType.ARRAY: {
|
|
||||||
Object[] messages = event.messages;
|
|
||||||
this.publisher.publish(syncPublication, messages);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,15 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus.publication.disruptor;
|
package dorkbox.util.messagebus.synchrony.disruptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/2/15
|
||||||
|
*/
|
||||||
public final class MessageType {
|
public final class MessageType {
|
||||||
public static final int ONE = 1;
|
public static final int ONE = 1;
|
||||||
public static final int TWO = 2;
|
public static final int TWO = 2;
|
||||||
public static final int THREE = 3;
|
public static final int THREE = 3;
|
||||||
public static final int ARRAY = 4;
|
|
||||||
|
|
||||||
private MessageType() {
|
private MessageType() {
|
||||||
}
|
}
|
|
@ -1,9 +1,27 @@
|
||||||
package dorkbox.util.messagebus.publication.disruptor;
|
/*
|
||||||
|
* Copyright 2016 dorkbox, llc
|
||||||
|
*
|
||||||
|
* 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 dorkbox.util.messagebus.synchrony.disruptor;
|
||||||
|
|
||||||
import com.lmax.disruptor.ExceptionHandler;
|
import com.lmax.disruptor.ExceptionHandler;
|
||||||
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
import dorkbox.util.messagebus.error.ErrorHandlingSupport;
|
||||||
import dorkbox.util.messagebus.error.PublicationError;
|
import dorkbox.util.messagebus.error.PublicationError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dorkbox, llc Date: 2/3/16
|
||||||
|
*/
|
||||||
public final class PublicationExceptionHandler<T> implements ExceptionHandler<T> {
|
public final class PublicationExceptionHandler<T> implements ExceptionHandler<T> {
|
||||||
private final ErrorHandlingSupport errorHandler;
|
private final ErrorHandlingSupport errorHandler;
|
||||||
|
|
|
@ -19,7 +19,6 @@ import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
public final
|
public final
|
||||||
|
@ -77,7 +76,6 @@ class ClassUtils {
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
c = superTypes[i];
|
c = superTypes[i];
|
||||||
|
|
||||||
c = getArrayClass(c);
|
c = getArrayClass(c);
|
||||||
|
|
||||||
if (c != clazz) {
|
if (c != clazz) {
|
||||||
|
@ -143,91 +141,4 @@ class ClassUtils {
|
||||||
this.arrayCache.clear();
|
this.arrayCache.clear();
|
||||||
this.superClassesCache.clear();
|
this.superClassesCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static
|
|
||||||
<T> ArrayList<T> findCommon(final T[] arrayOne, final T[] arrayTwo) {
|
|
||||||
|
|
||||||
T[] arrayToHash;
|
|
||||||
T[] arrayToSearch;
|
|
||||||
|
|
||||||
final int size1 = arrayOne.length;
|
|
||||||
final int size2 = arrayTwo.length;
|
|
||||||
|
|
||||||
final int hashSize;
|
|
||||||
final int searchSize;
|
|
||||||
|
|
||||||
if (size1 < size2) {
|
|
||||||
hashSize = size1;
|
|
||||||
searchSize = size2;
|
|
||||||
arrayToHash = arrayOne;
|
|
||||||
arrayToSearch = arrayTwo;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hashSize = size2;
|
|
||||||
searchSize = size1;
|
|
||||||
arrayToHash = arrayTwo;
|
|
||||||
arrayToSearch = arrayOne;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final ArrayList<T> intersection = new ArrayList<T>(searchSize);
|
|
||||||
|
|
||||||
final HashSet<T> hashedArray = new HashSet<T>();
|
|
||||||
for (int i = 0; i < hashSize; i++) {
|
|
||||||
T t = arrayToHash[i];
|
|
||||||
hashedArray.add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < searchSize; i++) {
|
|
||||||
T t = arrayToSearch[i];
|
|
||||||
if (hashedArray.contains(t)) {
|
|
||||||
intersection.add(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static
|
|
||||||
<T> ArrayList<T> findCommon(final ArrayList<T> arrayOne, final ArrayList<T> arrayTwo) {
|
|
||||||
|
|
||||||
ArrayList<T> arrayToHash;
|
|
||||||
ArrayList<T> arrayToSearch;
|
|
||||||
|
|
||||||
final int size1 = arrayOne.size();
|
|
||||||
final int size2 = arrayTwo.size();
|
|
||||||
|
|
||||||
final int hashSize;
|
|
||||||
final int searchSize;
|
|
||||||
|
|
||||||
if (size1 < size2) {
|
|
||||||
hashSize = size1;
|
|
||||||
searchSize = size2;
|
|
||||||
arrayToHash = arrayOne;
|
|
||||||
arrayToSearch = arrayTwo;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hashSize = size2;
|
|
||||||
searchSize = size1;
|
|
||||||
arrayToHash = arrayTwo;
|
|
||||||
arrayToSearch = arrayOne;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<T> intersection = new ArrayList<T>(searchSize);
|
|
||||||
|
|
||||||
HashSet<T> hashedArray = new HashSet<T>();
|
|
||||||
for (int i = 0; i < hashSize; i++) {
|
|
||||||
T t = arrayToHash.get(i);
|
|
||||||
hashedArray.add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < searchSize; i++) {
|
|
||||||
T t = arrayToSearch.get(i);
|
|
||||||
if (hashedArray.contains(t)) {
|
|
||||||
intersection.add(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return intersection;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package dorkbox.util.messagebus.utils;
|
package dorkbox.util.messagebus.utils;
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.util.IdentityMap;
|
import com.esotericsoftware.kryo.util.IdentityMap;
|
||||||
import dorkbox.util.messagebus.common.HashMapTree;
|
import dorkbox.util.messagebus.common.ClassTree;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class SubscriptionUtils {
|
||||||
|
|
||||||
// keeps track of all subscriptions of the super classes of a message type.
|
// keeps track of all subscriptions of the super classes of a message type.
|
||||||
private volatile IdentityMap<Class<?>, Subscription[]> superClassSubscriptions;
|
private volatile IdentityMap<Class<?>, Subscription[]> superClassSubscriptions;
|
||||||
private final HashMapTree<Class<?>, ArrayList<Subscription>> superClassSubscriptionsMulti;
|
private final ClassTree<Class<?>> superClassSubscriptionsMulti;
|
||||||
|
|
||||||
|
|
||||||
public
|
public
|
||||||
|
@ -43,7 +43,7 @@ class SubscriptionUtils {
|
||||||
// superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically.
|
// superClassSubscriptions keeps track of all subscriptions of super classes. SUB/UNSUB dumps it, so it is recreated dynamically.
|
||||||
// it's a hit on SUB/UNSUB, but improves performance of handlers
|
// it's a hit on SUB/UNSUB, but improves performance of handlers
|
||||||
this.superClassSubscriptions = new IdentityMap<Class<?>, Subscription[]>(8, loadFactor);
|
this.superClassSubscriptions = new IdentityMap<Class<?>, Subscription[]>(8, loadFactor);
|
||||||
this.superClassSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
|
this.superClassSubscriptionsMulti = new ClassTree<Class<?>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
public
|
||||||
|
@ -80,7 +80,7 @@ class SubscriptionUtils {
|
||||||
// for (int j = 0; j < superSubLength; j++) {
|
// for (int j = 0; j < superSubLength; j++) {
|
||||||
// sub = superSubs[j];
|
// sub = superSubs[j];
|
||||||
//
|
//
|
||||||
// if (sub.getHandler().acceptsSubtypes()) {
|
// if (sub.getHandlerAccess().acceptsSubtypes()) {
|
||||||
// subsAsList.add(sub);
|
// subsAsList.add(sub);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -123,61 +123,62 @@ class SubscriptionUtils {
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final SubscriptionManager subManager) {
|
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final SubscriptionManager subManager) {
|
||||||
// whenever our subscriptions change, this map is cleared.
|
// // whenever our subscriptions change, this map is cleared.
|
||||||
final HashMapTree<Class<?>, ArrayList<Subscription>> cached = this.superClassSubscriptionsMulti;
|
// final MapTree<Class<?>, ArrayList<Subscription>> cached = this.superClassSubscriptionsMulti;
|
||||||
|
//
|
||||||
|
// ArrayList<Subscription> subs = cached.get(clazz1, clazz2);
|
||||||
|
//
|
||||||
|
// if (subs == null) {
|
||||||
|
// // types was not empty, so collect subscriptions for each type and collate them
|
||||||
|
//
|
||||||
|
// // save the subscriptions
|
||||||
|
// final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
|
||||||
|
// final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
|
||||||
|
//
|
||||||
|
// Class<?> superClass1;
|
||||||
|
// Class<?> superClass2;
|
||||||
|
// ArrayList<Subscription> superSubs;
|
||||||
|
// Subscription sub;
|
||||||
|
//
|
||||||
|
// final int length1 = superClasses1.length;
|
||||||
|
// final int length2 = superClasses2.length;
|
||||||
|
//
|
||||||
|
// subs = new ArrayList<Subscription>(length1 + length2);
|
||||||
|
//
|
||||||
|
// for (int i = 0; i < length1; i++) {
|
||||||
|
// superClass1 = superClasses1[i];
|
||||||
|
//
|
||||||
|
// // only go over subtypes
|
||||||
|
// if (superClass1 == clazz1) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (int j = 0; j < length2; j++) {
|
||||||
|
// superClass2 = superClasses2[j];
|
||||||
|
//
|
||||||
|
// // only go over subtypes
|
||||||
|
// if (superClass2 == clazz2) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// superSubs = subManager.getExactAsArray(superClass1, superClass2);
|
||||||
|
// if (superSubs != null) {
|
||||||
|
// for (int k = 0; k < superSubs.size(); k++) {
|
||||||
|
// sub = superSubs.get(k);
|
||||||
|
//
|
||||||
|
// if (sub.getHandlerAccess().acceptsSubtypes()) {
|
||||||
|
// subs.add(sub);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// subs.trimToSize();
|
||||||
|
// cached.put(subs, clazz1, clazz2);
|
||||||
|
// }
|
||||||
|
|
||||||
ArrayList<Subscription> subs = cached.get(clazz1, clazz2);
|
// return subs;
|
||||||
|
return null;
|
||||||
if (subs == null) {
|
|
||||||
// types was not empty, so collect subscriptions for each type and collate them
|
|
||||||
|
|
||||||
// save the subscriptions
|
|
||||||
final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
|
|
||||||
final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
|
|
||||||
|
|
||||||
Class<?> superClass1;
|
|
||||||
Class<?> superClass2;
|
|
||||||
ArrayList<Subscription> superSubs;
|
|
||||||
Subscription sub;
|
|
||||||
|
|
||||||
final int length1 = superClasses1.length;
|
|
||||||
final int length2 = superClasses2.length;
|
|
||||||
|
|
||||||
subs = new ArrayList<Subscription>(length1 + length2);
|
|
||||||
|
|
||||||
for (int i = 0; i < length1; i++) {
|
|
||||||
superClass1 = superClasses1[i];
|
|
||||||
|
|
||||||
// only go over subtypes
|
|
||||||
if (superClass1 == clazz1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = 0; j < length2; j++) {
|
|
||||||
superClass2 = superClasses2[j];
|
|
||||||
|
|
||||||
// only go over subtypes
|
|
||||||
if (superClass2 == clazz2) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
superSubs = subManager.getExactAsArray(superClass1, superClass2);
|
|
||||||
if (superSubs != null) {
|
|
||||||
for (int k = 0; k < superSubs.size(); k++) {
|
|
||||||
sub = superSubs.get(k);
|
|
||||||
|
|
||||||
if (sub.getHandler().acceptsSubtypes()) {
|
|
||||||
subs.add(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subs.trimToSize();
|
|
||||||
cached.put(subs, clazz1, clazz2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return subs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,72 +191,73 @@ class SubscriptionUtils {
|
||||||
public
|
public
|
||||||
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final Class<?> clazz3,
|
ArrayList<Subscription> getSuperSubscriptions(final Class<?> clazz1, final Class<?> clazz2, final Class<?> clazz3,
|
||||||
final SubscriptionManager subManager) {
|
final SubscriptionManager subManager) {
|
||||||
// whenever our subscriptions change, this map is cleared.
|
// // whenever our subscriptions change, this map is cleared.
|
||||||
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptionsMulti;
|
// final MapTree<Class<?>, ArrayList<Subscription>> local = this.superClassSubscriptionsMulti;
|
||||||
|
//
|
||||||
ArrayList<Subscription> subs = local.get(clazz1, clazz2, clazz3);
|
// ArrayList<Subscription> subs = local.get(clazz1, clazz2, clazz3);
|
||||||
|
//
|
||||||
if (subs == null) {
|
// if (subs == null) {
|
||||||
// types was not empty, so collect subscriptions for each type and collate them
|
// // types was not empty, so collect subscriptions for each type and collate them
|
||||||
|
//
|
||||||
// save the subscriptions
|
// // save the subscriptions
|
||||||
final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
|
// final Class<?>[] superClasses1 = this.superClass.getSuperClasses(clazz1); // never returns null, cached response
|
||||||
final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
|
// final Class<?>[] superClasses2 = this.superClass.getSuperClasses(clazz2); // never returns null, cached response
|
||||||
final Class<?>[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response
|
// final Class<?>[] superClasses3 = this.superClass.getSuperClasses(clazz3); // never returns null, cached response
|
||||||
|
//
|
||||||
Class<?> superClass1;
|
// Class<?> superClass1;
|
||||||
Class<?> superClass2;
|
// Class<?> superClass2;
|
||||||
Class<?> superClass3;
|
// Class<?> superClass3;
|
||||||
ArrayList<Subscription> superSubs;
|
// ArrayList<Subscription> superSubs;
|
||||||
Subscription sub;
|
// Subscription sub;
|
||||||
|
//
|
||||||
final int length1 = superClasses1.length;
|
// final int length1 = superClasses1.length;
|
||||||
final int length2 = superClasses2.length;
|
// final int length2 = superClasses2.length;
|
||||||
final int length3 = superClasses3.length;
|
// final int length3 = superClasses3.length;
|
||||||
|
//
|
||||||
subs = new ArrayList<Subscription>(length1 + length2 + length3);
|
// subs = new ArrayList<Subscription>(length1 + length2 + length3);
|
||||||
|
//
|
||||||
for (int i = 0; i < length1; i++) {
|
// for (int i = 0; i < length1; i++) {
|
||||||
superClass1 = superClasses1[i];
|
// superClass1 = superClasses1[i];
|
||||||
|
//
|
||||||
// only go over subtypes
|
// // only go over subtypes
|
||||||
if (superClass1 == clazz1) {
|
// if (superClass1 == clazz1) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
for (int j = 0; j < length2; j++) {
|
// for (int j = 0; j < length2; j++) {
|
||||||
superClass2 = superClasses2[j];
|
// superClass2 = superClasses2[j];
|
||||||
|
//
|
||||||
// only go over subtypes
|
// // only go over subtypes
|
||||||
if (superClass2 == clazz2) {
|
// if (superClass2 == clazz2) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
for (int k = 0; k < length3; k++) {
|
// for (int k = 0; k < length3; k++) {
|
||||||
superClass3 = superClasses3[j];
|
// superClass3 = superClasses3[j];
|
||||||
|
//
|
||||||
// only go over subtypes
|
// // only go over subtypes
|
||||||
if (superClass3 == clazz3) {
|
// if (superClass3 == clazz3) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3);
|
// superSubs = subManager.getExactAsArray(superClass1, superClass2, superClass3);
|
||||||
if (superSubs != null) {
|
// if (superSubs != null) {
|
||||||
for (int m = 0; m < superSubs.size(); m++) {
|
// for (int m = 0; m < superSubs.size(); m++) {
|
||||||
sub = superSubs.get(m);
|
// sub = superSubs.get(m);
|
||||||
|
//
|
||||||
if (sub.getHandler().acceptsSubtypes()) {
|
// if (sub.getHandlerAccess().acceptsSubtypes()) {
|
||||||
subs.add(sub);
|
// subs.add(sub);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
subs.trimToSize();
|
// subs.trimToSize();
|
||||||
local.put(subs, clazz1, clazz2, clazz3);
|
// local.put(subs, clazz1, clazz2, clazz3);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return subs;
|
// return subs;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,224 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 dorkbox, llc
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.messagebus.utils;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.HashMapTree;
|
|
||||||
import dorkbox.util.messagebus.common.MessageHandler;
|
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
|
||||||
import dorkbox.util.messagebus.subscription.SubscriptionManager;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public final
|
|
||||||
class VarArgUtils {
|
|
||||||
private final Map<Class<?>, Subscription[]> varArgSubscriptionsSingle;
|
|
||||||
private final HashMapTree<Class<?>, ArrayList<Subscription>> varArgSubscriptionsMulti;
|
|
||||||
|
|
||||||
private final Map<Class<?>, Subscription[]> varArgSuperSubscriptionsSingle;
|
|
||||||
private final HashMapTree<Class<?>, ArrayList<Subscription>> varArgSuperSubscriptionsMulti;
|
|
||||||
|
|
||||||
private final ClassUtils superClassUtils;
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
VarArgUtils(final ClassUtils superClassUtils, final float loadFactor, final int numberOfThreads) {
|
|
||||||
|
|
||||||
this.superClassUtils = superClassUtils;
|
|
||||||
|
|
||||||
this.varArgSubscriptionsSingle = new ConcurrentHashMap<Class<?>, Subscription[]>(16, loadFactor, numberOfThreads);
|
|
||||||
this.varArgSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
|
|
||||||
|
|
||||||
this.varArgSuperSubscriptionsSingle = new ConcurrentHashMap<Class<?>, Subscription[]>(16, loadFactor, numberOfThreads);
|
|
||||||
this.varArgSuperSubscriptionsMulti = new HashMapTree<Class<?>, ArrayList<Subscription>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public
|
|
||||||
void clear() {
|
|
||||||
this.varArgSubscriptionsSingle.clear();
|
|
||||||
this.varArgSubscriptionsMulti.clear();
|
|
||||||
|
|
||||||
this.varArgSuperSubscriptionsSingle.clear();
|
|
||||||
this.varArgSuperSubscriptionsMulti.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// CAN NOT RETURN NULL
|
|
||||||
// check to see if the messageType can convert/publish to the "array" version, without the hit to JNI
|
|
||||||
// and then, returns the array'd version subscriptions
|
|
||||||
public
|
|
||||||
Subscription[] getVarArgSubscriptions(final Class<?> messageClass, final SubscriptionManager subManager) {
|
|
||||||
// whenever our subscriptions change, this map is cleared.
|
|
||||||
final Map<Class<?>, Subscription[]> local = this.varArgSubscriptionsSingle;
|
|
||||||
|
|
||||||
Subscription[] varArgSubs = local.get(messageClass);
|
|
||||||
|
|
||||||
if (varArgSubs == null) {
|
|
||||||
// this gets (and caches) our array type. This is never cleared.
|
|
||||||
final Class<?> arrayVersion = this.superClassUtils.getArrayClass(messageClass);
|
|
||||||
|
|
||||||
final Subscription[] subs = subManager.getSubs(arrayVersion);
|
|
||||||
if (subs != null) {
|
|
||||||
final int length = subs.length;
|
|
||||||
final ArrayList<Subscription> varArgSubsAsList = new ArrayList<Subscription>(length);
|
|
||||||
|
|
||||||
Subscription sub;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
sub = subs[i];
|
|
||||||
|
|
||||||
if (sub.getHandler().acceptsVarArgs()) {
|
|
||||||
varArgSubsAsList.add(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
varArgSubs = new Subscription[0];
|
|
||||||
varArgSubsAsList.toArray(varArgSubs);
|
|
||||||
|
|
||||||
local.put(messageClass, varArgSubs);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
varArgSubs = new Subscription[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return varArgSubs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// CAN NOT RETURN NULL
|
|
||||||
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
|
||||||
// and then, returns the array'd version superclass subscriptions
|
|
||||||
public
|
|
||||||
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass, final SubscriptionManager subManager) {
|
|
||||||
// whenever our subscriptions change, this map is cleared.
|
|
||||||
final Map<Class<?>, Subscription[]> local = this.varArgSuperSubscriptionsSingle;
|
|
||||||
|
|
||||||
Subscription[] varArgSuperSubs = local.get(messageClass);
|
|
||||||
|
|
||||||
if (varArgSuperSubs == null) {
|
|
||||||
// this gets (and caches) our array type. This is never cleared.
|
|
||||||
final Class<?> arrayVersion = this.superClassUtils.getArrayClass(messageClass);
|
|
||||||
final Class<?>[] types = this.superClassUtils.getSuperClasses(arrayVersion);
|
|
||||||
|
|
||||||
final int typesLength = types.length;
|
|
||||||
varArgSuperSubs = new Subscription[typesLength];
|
|
||||||
|
|
||||||
if (typesLength == 0) {
|
|
||||||
local.put(messageClass, varArgSuperSubs);
|
|
||||||
return varArgSuperSubs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// there are varArgs super classes for this messageClass
|
|
||||||
Class<?> type;
|
|
||||||
Subscription sub;
|
|
||||||
Subscription[] subs;
|
|
||||||
int length;
|
|
||||||
MessageHandler handlerMetadata;
|
|
||||||
|
|
||||||
for (int i = 0; i < typesLength; i++) {
|
|
||||||
type = types[i];
|
|
||||||
subs = subManager.getSubs(type);
|
|
||||||
|
|
||||||
if (subs != null) {
|
|
||||||
length = subs.length;
|
|
||||||
final ArrayList<Subscription> varArgSuperSubsAsList = new ArrayList<Subscription>(length);
|
|
||||||
|
|
||||||
for (int j = 0; j < length; j++) {
|
|
||||||
sub = subs[j];
|
|
||||||
|
|
||||||
handlerMetadata = sub.getHandler();
|
|
||||||
if (handlerMetadata.acceptsSubtypes() && handlerMetadata.acceptsVarArgs()) {
|
|
||||||
varArgSuperSubsAsList.add(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
varArgSuperSubs = new Subscription[varArgSuperSubsAsList.size()];
|
|
||||||
varArgSuperSubsAsList.toArray(varArgSuperSubs);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
varArgSuperSubs = new Subscription[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local.put(messageClass, varArgSuperSubs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return varArgSuperSubs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// CAN NOT RETURN NULL
|
|
||||||
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
|
||||||
// and then, returns the array'd version superclass subscriptions
|
|
||||||
public
|
|
||||||
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final SubscriptionManager subManager) {
|
|
||||||
// whenever our subscriptions change, this map is cleared.
|
|
||||||
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.varArgSuperSubscriptionsMulti;
|
|
||||||
|
|
||||||
ArrayList<Subscription> subs = local.get(messageClass1, messageClass2);
|
|
||||||
|
|
||||||
// if (subs == null) {
|
|
||||||
// // the message class types are not the same, so look for a common superClass varArg subscription.
|
|
||||||
// // this is to publish to object[] (or any class[]) handler that is common among all superTypes of the messages
|
|
||||||
// final ArrayList<Subscription> varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager);
|
|
||||||
// final ArrayList<Subscription> varargSuperSubscriptions2 = getVarArgSuperSubscriptions_List(messageClass2, subManager);
|
|
||||||
//
|
|
||||||
// subs = ClassUtils.findCommon(varargSuperSubscriptions1, varargSuperSubscriptions2);
|
|
||||||
//
|
|
||||||
// subs.trimToSize();
|
|
||||||
// local.put(subs, messageClass1, messageClass2);
|
|
||||||
// }
|
|
||||||
|
|
||||||
final Subscription[] returnedSubscriptions = new Subscription[subs.size()];
|
|
||||||
subs.toArray(returnedSubscriptions);
|
|
||||||
return returnedSubscriptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// CAN NOT RETURN NULL
|
|
||||||
// check to see if the messageType can convert/publish to the "array" superclass version, without the hit to JNI
|
|
||||||
// and then, returns the array'd version superclass subscriptions
|
|
||||||
public
|
|
||||||
Subscription[] getVarArgSuperSubscriptions(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3,
|
|
||||||
final SubscriptionManager subManager) {
|
|
||||||
// whenever our subscriptions change, this map is cleared.
|
|
||||||
final HashMapTree<Class<?>, ArrayList<Subscription>> local = this.varArgSuperSubscriptionsMulti;
|
|
||||||
|
|
||||||
ArrayList<Subscription> subs = local.get(messageClass1, messageClass2, messageClass3);
|
|
||||||
|
|
||||||
// if (subs == null) {
|
|
||||||
// // the message class types are not the same, so look for a common superClass varArg subscription.
|
|
||||||
// // this is to publish to object[] (or any class[]) handler that is common among all superTypes of the messages
|
|
||||||
// final ArrayList<Subscription> varargSuperSubscriptions1 = getVarArgSuperSubscriptions_List(messageClass1, subManager);
|
|
||||||
// final ArrayList<Subscription> varargSuperSubscriptions2 = getVarArgSuperSubscriptions_List(messageClass2, subManager);
|
|
||||||
// final ArrayList<Subscription> varargSuperSubscriptions3 = getVarArgSuperSubscriptions_List(messageClass3, subManager);
|
|
||||||
//
|
|
||||||
// subs = ClassUtils.findCommon(varargSuperSubscriptions1, varargSuperSubscriptions2);
|
|
||||||
// subs = ClassUtils.findCommon(subs, varargSuperSubscriptions3);
|
|
||||||
//
|
|
||||||
// subs.trimToSize();
|
|
||||||
// local.put(subs, messageClass1, messageClass2, messageClass3);
|
|
||||||
// }
|
|
||||||
|
|
||||||
final Subscription[] returnedSubscriptions = new Subscription[subs.size()];
|
|
||||||
subs.toArray(returnedSubscriptions);
|
|
||||||
return returnedSubscriptions;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package dorkbox.util.messagebus.perfTests;
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public class MpmcArrayQueue extends Base_BlockingQueue {
|
|
||||||
|
|
||||||
public static final int REPETITIONS = 50 * 1000 * 100;
|
|
||||||
|
|
||||||
private static final int bestRunsToAverage = 4;
|
|
||||||
private static final int runs = 10;
|
|
||||||
private static final int warmups = 3;
|
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
|
||||||
System.out.format("reps: %,d %s: \n", REPETITIONS, MpmcArrayQueue.class.getSimpleName());
|
|
||||||
|
|
||||||
for (int concurrency = 1; concurrency < 5; concurrency++) {
|
|
||||||
final org.jctools.queues.MpmcArrayQueue queue = new org.jctools.queues.MpmcArrayQueue(1 << 17);
|
|
||||||
final Integer initialValue = Integer.valueOf(777);
|
|
||||||
new MpmcArray_NonBlock().run(REPETITIONS, concurrency, concurrency, warmups, runs, bestRunsToAverage, false, queue,
|
|
||||||
initialValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
class MpmcArray_NonBlock extends Base_Queue<Integer> {}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user