734 lines
33 KiB
Java
734 lines
33 KiB
Java
/*
|
|
* 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.messageBus.subscription;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|
|
|
import dorkbox.collections.IdentityMap;
|
|
import dorkbox.messageBus.SubscriptionMode;
|
|
import dorkbox.messageBus.common.ClassTree;
|
|
import dorkbox.messageBus.common.MessageHandler;
|
|
import dorkbox.messageBus.common.MultiClass;
|
|
import dorkbox.messageBus.subscription.asm.AsmFactory;
|
|
import dorkbox.messageBus.subscription.reflection.ReflectionFactory;
|
|
import dorkbox.util.classes.ClassHierarchy;
|
|
|
|
|
|
/**
|
|
* 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.
|
|
* 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
|
|
* message handlers.
|
|
*
|
|
* @author dorkbox, llc
|
|
* Date: 2/2/15
|
|
*/
|
|
@SuppressWarnings({"unchecked", "ToArrayCallWithZeroLengthArrayArgument", "ForLoopReplaceableByForEach", "Duplicates"})
|
|
public final
|
|
class SubscriptionManager {
|
|
public static final float LOAD_FACTOR = 0.8F;
|
|
private static final Subscription[] EMPTY_SUBS = new Subscription[0];
|
|
|
|
// controls if we use java reflection or ASM to access methods during publication
|
|
private final SubscriptionFactory subscriptionFactory;
|
|
|
|
|
|
// ONLY used by SUB/UNSUB
|
|
// remember already processed classes that do not contain any message handlers
|
|
private final IdentityMap<Class<?>, Boolean> nonListeners;
|
|
|
|
// ONLY used by SUB/UNSUB
|
|
// all subscriptions per messageHandler type
|
|
// this map provides fast access for subscribing and unsubscribing
|
|
// once a collection of subscriptions is stored it does not change
|
|
private final IdentityMap<Class<?>, Subscription[]> subsPerListener;
|
|
|
|
// We perpetually KEEP the types registered here, and just change what is sub/unsub
|
|
|
|
// all subscriptions of a message type.
|
|
private volatile IdentityMap<Class<?>, Subscription[]> subsSingle;
|
|
private volatile IdentityMap<MultiClass, Subscription[]> subsMulti;
|
|
|
|
// keeps track of all subscriptions of the super classes of a message type.
|
|
private volatile IdentityMap<Class<?>, Subscription[]> subsSuperSingle;
|
|
private volatile IdentityMap<MultiClass, Subscription[]> subsSuperMulti;
|
|
|
|
// In order to force the "single writer principle" for subscribe & unsubscribe, they are within SYNCHRONIZED.
|
|
//
|
|
// These methods **COULD** be dispatched via another thread (so it's only one thread ever touching them), however we do NOT want them
|
|
// asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is called before. 'Synchronized' is good enough here.
|
|
private final Object singleWriterLock = new Object();
|
|
|
|
|
|
private final ClassTree<Class<?>> classTree;
|
|
private final ClassHierarchy classHierarchyUtils;
|
|
|
|
|
|
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
|
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsSingleREF =
|
|
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
IdentityMap.class,
|
|
"subsSingle");
|
|
|
|
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsMultiREF =
|
|
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
IdentityMap.class,
|
|
"subsMulti");
|
|
|
|
|
|
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsSuperSingleREF =
|
|
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
IdentityMap.class,
|
|
"subsSuperSingle");
|
|
|
|
private static final AtomicReferenceFieldUpdater<SubscriptionManager, IdentityMap> subsSuperMultiREF =
|
|
AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class,
|
|
IdentityMap.class,
|
|
"subsSuperMulti");
|
|
|
|
/**
|
|
* By default, we use ASM for accessing methods during the dispatch of messages. This is only available on certain platforms, and so
|
|
* it will gracefully 'fallback' to using standard java reflection to access the methods. "Standard java reflection" is not as fast
|
|
* as ASM, but only marginally.
|
|
*
|
|
* If you would like to use java reflection for accessing methods, set this value to false.
|
|
*/
|
|
public static boolean useAsmForDispatch = 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 than the defaults
|
|
try {
|
|
Class.forName("com.esotericsoftware.reflectasm.MethodAccess");
|
|
} catch (Exception e) {
|
|
useAsmForDispatch = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public
|
|
SubscriptionManager(final SubscriptionMode subscriptionMode) {
|
|
boolean useStrongReferences = subscriptionMode == SubscriptionMode.StrongReferences;
|
|
|
|
|
|
// not all platforms support ASM. ASM is our default, and is just-as-fast and directly invoking the method
|
|
if (useAsmForDispatch) {
|
|
this.subscriptionFactory = new AsmFactory(useStrongReferences);
|
|
}
|
|
else {
|
|
this.subscriptionFactory = new ReflectionFactory(useStrongReferences);
|
|
}
|
|
|
|
classHierarchyUtils = new ClassHierarchy(LOAD_FACTOR);
|
|
classTree = new ClassTree<Class<?>>();
|
|
|
|
|
|
// modified ONLY during SUB/UNSUB
|
|
nonListeners = new IdentityMap<Class<?>, Boolean>(16, LOAD_FACTOR);
|
|
subsPerListener = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
|
subsSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
|
subsMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
|
|
|
|
|
|
// modified during publication, however duplicates are OK, we we can "pretend" it's the same as the single-writer-principle
|
|
subsSuperSingle = new IdentityMap<Class<?>, Subscription[]>(32, LOAD_FACTOR);
|
|
subsSuperMulti = new IdentityMap<MultiClass, Subscription[]>(32, LOAD_FACTOR);
|
|
}
|
|
|
|
/**
|
|
* Shuts down and clears all memory usage by the subscriptions
|
|
*/
|
|
public
|
|
void shutdown() {
|
|
|
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
// use-case 99% of the time)
|
|
synchronized (singleWriterLock) {
|
|
// explicitly clear out the subscriptions
|
|
final IdentityMap.Entries<Class<?>, Subscription[]> entries = subsPerListener.entries();
|
|
for (IdentityMap.Entry<Class<?>, Subscription[]> entry : entries) {
|
|
final Subscription[] subscriptions = entry.value;
|
|
if (subscriptions != null) {
|
|
Subscription subscription;
|
|
|
|
for (int i = 0; i < subscriptions.length; i++) {
|
|
subscription = subscriptions[i];
|
|
subscription.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.nonListeners.clear();
|
|
|
|
this.subsPerListener.clear();
|
|
|
|
this.subsSingle.clear();
|
|
this.subsMulti.clear();
|
|
|
|
this.subsSuperSingle.clear();
|
|
this.subsSuperMulti.clear();
|
|
|
|
this.classTree.clear();
|
|
this.classHierarchyUtils.shutdown();
|
|
}
|
|
|
|
/**
|
|
* Subscribes a specific listener. The infrastructure for subscription never "shrinks", meaning that when a listener is un-subscribed,
|
|
* the listeners are only removed from the internal map -- the map itself is not cleaned up until a 'shutdown' is called.
|
|
*
|
|
* This method uses the "single-writer-principle" for lock-free publication. Since there are only 2
|
|
* methods to guarantee this method can only be called one-at-a-time (either it is only called by one thread, or only one thread can
|
|
* access it at a time) -- we chose the 2nd option -- and use a 'synchronized' block to make sure that only one thread can access
|
|
* this method at a time.
|
|
*/
|
|
public
|
|
void subscribe(final Object listener) {
|
|
if (listener == null) {
|
|
return;
|
|
}
|
|
|
|
final Class<?> listenerClass = listener.getClass();
|
|
|
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
// use-case 99% of the time)
|
|
synchronized (singleWriterLock) {
|
|
final IdentityMap<Class<?>, Boolean> nonListeners = this.nonListeners;
|
|
if (nonListeners.containsKey(listenerClass)) {
|
|
// early reject of known classes that do not define message handlers
|
|
return;
|
|
}
|
|
|
|
// this is an array, because subscriptions for a specific listener CANNOT change, either they exist or do not exist.
|
|
// ONCE subscriptions are in THIS map, they are considered AVAILABLE.
|
|
Subscription[] subscriptions = subsPerListener.get(listenerClass);
|
|
|
|
// the subscriptions from the map were null, so create them
|
|
if (subscriptions == null) {
|
|
final MessageHandler[] messageHandlers = MessageHandler.get(listenerClass);
|
|
final int handlersSize = messageHandlers.length;
|
|
|
|
// remember the class as non listening class if no handlers are found
|
|
if (handlersSize == 0) {
|
|
this.nonListeners.put(listenerClass, Boolean.TRUE);
|
|
return;
|
|
}
|
|
|
|
// create the subscriptions
|
|
subscriptions = new Subscription[handlersSize];
|
|
|
|
// access a snapshot of the subscriptions (single-writer-principle)
|
|
final IdentityMap<Class<?>, Subscription[]> singleSubs = subsSingleREF.get(this);
|
|
final IdentityMap<MultiClass, Subscription[]> multiSubs = subsMultiREF.get(this);
|
|
|
|
Subscription subscription;
|
|
|
|
MessageHandler messageHandler;
|
|
Class<?>[] messageHandlerTypes;
|
|
int messageHandlerTypesSize;
|
|
|
|
MultiClass multiClass;
|
|
Class<?> handlerType;
|
|
|
|
|
|
// Prepare all of the subscriptions and add for publication AND subscribe since the data structures are consistent
|
|
for (int i = 0; i < handlersSize; i++) {
|
|
messageHandler = messageHandlers[i];
|
|
|
|
subscription = subscriptionFactory.create(listenerClass, messageHandler);
|
|
subscription.subscribe(listener); // register this callback listener to this subscription
|
|
subscriptions[i] = subscription;
|
|
|
|
// register for publication
|
|
messageHandlerTypes = messageHandler.getHandledMessages();
|
|
messageHandlerTypesSize = messageHandlerTypes.length;
|
|
|
|
switch (messageHandlerTypesSize) {
|
|
case 0: {
|
|
// if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed)
|
|
// This is the SAME THING as having Void as a parameter!!
|
|
handlerType = Void.class;
|
|
|
|
|
|
// makes this subscription visible for publication
|
|
final Subscription[] newSubs;
|
|
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;
|
|
}
|
|
|
|
singleSubs.put(handlerType, newSubs);
|
|
break;
|
|
}
|
|
|
|
case 1: {
|
|
handlerType = messageHandlerTypes[0];
|
|
|
|
// makes this subscription visible for publication
|
|
final Subscription[] newSubs;
|
|
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;
|
|
}
|
|
|
|
singleSubs.put(handlerType, newSubs);
|
|
|
|
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: {
|
|
throw new RuntimeException("Unsupported number of parameters during subscribe. Acceptable max is 3");
|
|
}
|
|
}
|
|
}
|
|
|
|
// activates this sub for sub/unsub (only used by the subscription writer thread)
|
|
subsPerListener.put(listenerClass, subscriptions);
|
|
|
|
|
|
// save this snapshot back to the original (single writer principle)
|
|
subsSingleREF.lazySet(this, singleSubs);
|
|
subsMultiREF.lazySet(this, multiSubs);
|
|
|
|
|
|
// only dump the super subscriptions if it is a COMPLETELY NEW subscription.
|
|
// If it's not new, then the hierarchy isn't changing for super subscriptions
|
|
IdentityMap<Class<?>, Subscription[]> superSingleSubs = subsSuperSingleREF.get(this);
|
|
superSingleSubs.clear();
|
|
subsSuperSingleREF.lazySet(this, superSingleSubs);
|
|
|
|
IdentityMap<MultiClass, Subscription[]> superMultiSubs = subsSuperMultiREF.get(this);
|
|
superMultiSubs.clear();
|
|
subsSuperMultiREF.lazySet(this, superMultiSubs);
|
|
}
|
|
else {
|
|
// subscriptions already exist and must only be updated
|
|
Subscription subscription;
|
|
for (int i = 0; i < subscriptions.length; i++) {
|
|
subscription = subscriptions[i];
|
|
subscription.subscribe(listener);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Un-subscribes a specific listener. The infrastructure for subscription never "shrinks", meaning that when a listener is un-subscribed,
|
|
* the listeners are only removed from the internal map -- the map itself is not cleaned up until a 'shutdown' is called.
|
|
*
|
|
* This method uses the "single-writer-principle" for lock-free publication. Since there are only 2
|
|
* methods to guarantee this method can only be called one-at-a-time (either it is only called by one thread, or only one thread can
|
|
* access it at a time) -- we chose the 2nd option -- and use a 'synchronized' block to make sure that only one thread can access
|
|
* this method at a time.
|
|
*/
|
|
public
|
|
void unsubscribe(final Object listener) {
|
|
if (listener == null) {
|
|
return;
|
|
}
|
|
|
|
final Class<?> listenerClass = listener.getClass();
|
|
|
|
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
|
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
|
// use-case 99% of the time)
|
|
synchronized (singleWriterLock) {
|
|
if (nonListeners.containsKey(listenerClass)) {
|
|
// early reject of known classes that do not define message handlers
|
|
return;
|
|
}
|
|
|
|
final Subscription[] subscriptions = subsPerListener.get(listenerClass);
|
|
if (subscriptions != null) {
|
|
Subscription subscription;
|
|
|
|
for (int i = 0; i < subscriptions.length; i++) {
|
|
subscription = subscriptions[i];
|
|
subscription.unsubscribe(listener);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @return can return null
|
|
*/
|
|
public
|
|
Subscription[] getSubs(final Class<?> messageClass) {
|
|
return (Subscription[]) subsSingleREF.get(this).get(messageClass);
|
|
}
|
|
|
|
|
|
/**
|
|
* @return can return null
|
|
*/
|
|
public
|
|
Subscription[] getSubs(final Class<?> messageClass1, final Class<?> messageClass2) {
|
|
// never returns null
|
|
final MultiClass multiClass = classTree.get(messageClass1,
|
|
messageClass2);
|
|
return (Subscription[]) subsMultiREF.get(this).get(multiClass);
|
|
}
|
|
|
|
/**
|
|
* @return can return null
|
|
*/
|
|
public
|
|
Subscription[] getSubs(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3) {
|
|
// never returns null
|
|
final MultiClass multiClass = classTree.get(messageClass1,
|
|
messageClass2,
|
|
messageClass3);
|
|
return (Subscription[]) subsMultiREF.get(this).get(multiClass);
|
|
}
|
|
|
|
/**
|
|
* @return can NOT return null
|
|
*/
|
|
public
|
|
Subscription[] getSuperSubs(final Class<?> messageClass) {
|
|
// 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);
|
|
|
|
Subscription[] subscriptions = localSuperSubs.get(messageClass);
|
|
|
|
// the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are
|
|
// calculated and if they do not exist - this will be an empty array.
|
|
if (subscriptions == null) {
|
|
final Class<?>[] superClasses = this.classHierarchyUtils.getClassAndSuperClasses(messageClass); // never returns null, cached response
|
|
|
|
final int length = superClasses.length;
|
|
final ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length);
|
|
|
|
final IdentityMap<Class<?>, Subscription[]> localSubs = subsSingleREF.get(this);
|
|
|
|
Class<?> superClass;
|
|
Subscription sub;
|
|
Subscription[] superSubs;
|
|
|
|
MessageHandler handler;
|
|
Class<?>[] handledMessages;
|
|
boolean acceptsSubtypes;
|
|
Class<?> handledMessage;
|
|
|
|
// walks through all of the subscriptions that might exist for super types, and if applicable, save them.
|
|
for (int i = 0; i < length; i++) {
|
|
superClass = superClasses[i];
|
|
|
|
// only go over subtypes (NON-EXACT class signature matches)
|
|
if (superClass == messageClass) {
|
|
continue;
|
|
}
|
|
|
|
// check to see if we have a subscription for this
|
|
superSubs = localSubs.get(superClass);
|
|
|
|
if (superSubs != null) {
|
|
int superSubLength = superSubs.length;
|
|
for (int j = 0; j < superSubLength; j++) {
|
|
sub = superSubs[j];
|
|
|
|
handler = sub.getHandler();
|
|
handledMessages = handler.getHandledMessages();
|
|
acceptsSubtypes = handler.acceptsSubtypes();
|
|
|
|
// check to see if our subscription can handle the superClass type. only 1 will exist for this subscription
|
|
handledMessage = handledMessages[0];
|
|
if (handledMessage.equals(messageClass)) {
|
|
// exact type
|
|
subsAsList.add(sub);
|
|
}
|
|
else if (acceptsSubtypes && handledMessage.isAssignableFrom(messageClass)) {
|
|
// legit sub-type
|
|
subsAsList.add(sub);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// subsAsList now contains ALL of the super-class subscriptions.
|
|
subscriptions = subsAsList.toArray(EMPTY_SUBS);
|
|
localSuperSubs.put(messageClass, subscriptions);
|
|
|
|
subsSuperSingleREF.lazySet(this, localSuperSubs);
|
|
}
|
|
|
|
return subscriptions;
|
|
}
|
|
|
|
/**
|
|
* @return can NOT return null
|
|
*/
|
|
public
|
|
Subscription[] getSuperSubs(final Class<?> messageClass1, final Class<?> messageClass2) {
|
|
// save the subscriptions
|
|
final Class<?>[] superClasses1 = this.classHierarchyUtils.getClassAndSuperClasses(messageClass1); // never returns null, cached response
|
|
final Class<?>[] superClasses2 = this.classHierarchyUtils.getClassAndSuperClasses(messageClass2); // never returns null, cached response
|
|
|
|
final MultiClass origMultiClass = classTree.get(messageClass1, messageClass2);
|
|
|
|
IdentityMap<MultiClass, Subscription[]> localSuperSubs = subsSuperMultiREF.get(this);
|
|
Subscription[] subscriptions = localSuperSubs.get(origMultiClass);
|
|
|
|
// the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are
|
|
// calculated and if they do not exist - this will be an empty array.
|
|
if (subscriptions == null) {
|
|
final IdentityMap<MultiClass, Subscription[]> localSubs = subsMultiREF.get(this);
|
|
|
|
Class<?> superClass1;
|
|
Class<?> superClass2;
|
|
Subscription sub;
|
|
Subscription[] superSubs;
|
|
|
|
MessageHandler handler;
|
|
Class<?>[] handledMessages;
|
|
boolean acceptsSubtypes;
|
|
Class<?> handledMessage1;
|
|
Class<?> handledMessage2;
|
|
|
|
final int length1 = superClasses1.length;
|
|
final int length2 = superClasses2.length;
|
|
|
|
ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length1 + length2);
|
|
|
|
for (int i = 0; i < length1; i++) {
|
|
superClass1 = superClasses1[i];
|
|
|
|
for (int j = 0; j < length2; j++) {
|
|
superClass2 = superClasses2[j];
|
|
|
|
// only go over subtypes (NON-EXACT class signature matches)
|
|
if (superClass1 == messageClass1 && superClass2 == messageClass2) {
|
|
continue;
|
|
}
|
|
|
|
// never returns null
|
|
MultiClass multiClass = classTree.get(superClass1,
|
|
superClass2);
|
|
|
|
// check to see if we have a subscription for this
|
|
superSubs = localSubs.get(multiClass);
|
|
|
|
if (superSubs != null) {
|
|
for (int k = 0; k < superSubs.length; k++) {
|
|
sub = superSubs[k];
|
|
|
|
handler = sub.getHandler();
|
|
handledMessages = handler.getHandledMessages();
|
|
acceptsSubtypes = handler.acceptsSubtypes();
|
|
|
|
handledMessage1 = handledMessages[0];
|
|
handledMessage2 = handledMessages[1];
|
|
|
|
if (handledMessage1.equals(messageClass1) &&
|
|
handledMessage2.equals(messageClass2)) {
|
|
// exact type
|
|
subsAsList.add(sub);
|
|
}
|
|
else if (acceptsSubtypes && handledMessage1.isAssignableFrom(messageClass1) &&
|
|
handledMessage2.isAssignableFrom(messageClass2)) {
|
|
// legit sub-type
|
|
subsAsList.add(sub);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// subsAsList now contains ALL of the super-class subscriptions.
|
|
subscriptions = subsAsList.toArray(EMPTY_SUBS);
|
|
localSuperSubs.put(origMultiClass, subscriptions);
|
|
|
|
subsSuperMultiREF.lazySet(this, localSuperSubs);
|
|
}
|
|
|
|
return subscriptions;
|
|
}
|
|
|
|
/**
|
|
* @return can NOT return null
|
|
*/
|
|
public
|
|
Subscription[] getSuperSubs(final Class<?> messageClass1, final Class<?> messageClass2, final Class<?> messageClass3) {
|
|
// save the subscriptions
|
|
final Class<?>[] superClasses1 = this.classHierarchyUtils.getClassAndSuperClasses(messageClass1); // never returns null, cached response
|
|
final Class<?>[] superClasses2 = this.classHierarchyUtils.getClassAndSuperClasses(messageClass2); // never returns null, cached response
|
|
final Class<?>[] superClasses3 = this.classHierarchyUtils.getClassAndSuperClasses(messageClass3); // never returns null, cached response
|
|
|
|
final MultiClass origMultiClass = classTree.get(messageClass1, messageClass2, messageClass3);
|
|
|
|
IdentityMap<MultiClass, Subscription[]> localSuperSubs = subsSuperMultiREF.get(this);
|
|
Subscription[] subscriptions = localSuperSubs.get(origMultiClass);
|
|
|
|
// the only time this is null, is when subscriptions DO NOT exist, and they haven't been calculated. Otherwise, if they are
|
|
// calculated and if they do not exist - this will be an empty array.
|
|
if (subscriptions == null) {
|
|
final IdentityMap<MultiClass, Subscription[]> localSubs = subsMultiREF.get(this);
|
|
|
|
Class<?> superClass1;
|
|
Class<?> superClass2;
|
|
Class<?> superClass3;
|
|
Subscription sub;
|
|
Subscription[] superSubs;
|
|
|
|
MessageHandler handler;
|
|
Class<?>[] handledMessages;
|
|
boolean acceptsSubtypes;
|
|
Class<?> handledMessage1;
|
|
Class<?> handledMessage2;
|
|
Class<?> handledMessage3;
|
|
|
|
final int length1 = superClasses1.length;
|
|
final int length2 = superClasses2.length;
|
|
final int length3 = superClasses3.length;
|
|
|
|
ArrayList<Subscription> subsAsList = new ArrayList<Subscription>(length1 + length2 + length3);
|
|
|
|
for (int i = 0; i < length1; i++) {
|
|
superClass1 = superClasses1[i];
|
|
|
|
for (int j = 0; j < length2; j++) {
|
|
superClass2 = superClasses2[j];
|
|
|
|
for (int k = 0; k < length3; k++) {
|
|
superClass3 = superClasses3[k];
|
|
|
|
|
|
// only go over subtypes (NON-EXACT class signature matches)
|
|
if (superClass1 == messageClass1 &&
|
|
superClass2 == messageClass2 &&
|
|
superClass3 == messageClass3) {
|
|
continue;
|
|
}
|
|
|
|
// never returns null
|
|
MultiClass multiClass = classTree.get(superClass1,
|
|
superClass2,
|
|
superClass3);
|
|
|
|
superSubs = localSubs.get(multiClass);
|
|
|
|
if (superSubs != null) {
|
|
for (int m = 0; m < superSubs.length; m++) {
|
|
sub = superSubs[m];
|
|
|
|
handler = sub.getHandler();
|
|
handledMessages = handler.getHandledMessages();
|
|
acceptsSubtypes = handler.acceptsSubtypes();
|
|
|
|
handledMessage1 = handledMessages[0];
|
|
handledMessage2 = handledMessages[1];
|
|
handledMessage3 = handledMessages[2];
|
|
|
|
if (handledMessage1.equals(messageClass1) &&
|
|
handledMessage2.equals(messageClass2) &&
|
|
handledMessage3.equals(messageClass3)) {
|
|
// exact type
|
|
subsAsList.add(sub);
|
|
}
|
|
else if (acceptsSubtypes && handledMessage1.isAssignableFrom(messageClass1) &&
|
|
handledMessage2.isAssignableFrom(messageClass2) &&
|
|
handledMessage3.isAssignableFrom(messageClass3)) {
|
|
// legit sub-type
|
|
subsAsList.add(sub);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// subsAsList now contains ALL of the super-class subscriptions.
|
|
subscriptions = subsAsList.toArray(EMPTY_SUBS);
|
|
localSuperSubs.put(origMultiClass, subscriptions);
|
|
|
|
subsSuperMultiREF.lazySet(this, localSuperSubs);
|
|
}
|
|
|
|
return subscriptions;
|
|
}
|
|
}
|