From 8c4d515c2131f37f05e4c51028a71d0c73acc6a9 Mon Sep 17 00:00:00 2001 From: benni Date: Tue, 23 Oct 2012 09:32:55 +0200 Subject: [PATCH] initial project setup --- mbassador.iml | 17 + pom.xml | 49 ++ src/main/java/org/mbassy/Listener.java | 27 ++ src/main/java/org/mbassy/MBassador.java | 427 ++++++++++++++++++ .../mbassy/common/ConcurrentLinkedBag.java | 159 +++++++ .../java/org/mbassy/common/IPredicate.java | 14 + .../common/IPublicationErrorHandler.java | 13 + .../org/mbassy/common/PublicationError.java | 98 ++++ .../org/mbassy/common/ReflectionUtils.java | 70 +++ src/main/java/org/mbassy/filter/Filter.java | 23 + .../java/org/mbassy/filter/MessageFilter.java | 31 ++ .../java/org/mbassy/ConcurrentExecutor.java | 68 +++ src/test/java/org/mbassy/MBassadorTest.java | 167 +++++++ .../org/mbassy/Listener$Dispatch.class | Bin 0 -> 1066 bytes target/classes/org/mbassy/Listener.class | Bin 0 -> 672 bytes target/classes/org/mbassy/MBassador$1.class | Bin 0 -> 959 bytes target/classes/org/mbassy/MBassador$2.class | Bin 0 -> 1478 bytes .../org/mbassy/MBassador$ConsoleLogger.class | Bin 0 -> 920 bytes .../org/mbassy/MBassador$Subscription$1.class | Bin 0 -> 987 bytes .../org/mbassy/MBassador$Subscription.class | Bin 0 -> 5232 bytes target/classes/org/mbassy/MBassador.class | Bin 0 -> 10950 bytes .../mbassy/common/ConcurrentLinkedBag$1.class | Bin 0 -> 1908 bytes .../ConcurrentLinkedBag$ListEntry.class | Bin 0 -> 2940 bytes .../mbassy/common/ConcurrentLinkedBag.class | Bin 0 -> 3544 bytes .../org/mbassy/common/IPredicate.class | Bin 0 -> 239 bytes .../common/IPublicationErrorHandler.class | Bin 0 -> 211 bytes .../org/mbassy/common/PublicationError.class | Bin 0 -> 2526 bytes .../org/mbassy/common/ReflectionUtils.class | Bin 0 -> 3388 bytes target/classes/org/mbassy/filter/Filter.class | Bin 0 -> 500 bytes .../org/mbassy/filter/MessageFilter$All.class | Bin 0 -> 554 bytes .../org/mbassy/filter/MessageFilter.class | Bin 0 -> 263 bytes .../org/mbassy/ConcurrentExecutor$1.class | Bin 0 -> 1108 bytes .../org/mbassy/ConcurrentExecutor.class | Bin 0 -> 2974 bytes .../org/mbassy/MBassadorTest$1.class | Bin 0 -> 1767 bytes .../org/mbassy/MBassadorTest$2.class | Bin 0 -> 1979 bytes .../MBassadorTest$EventingTestBean.class | Bin 0 -> 1409 bytes .../mbassy/MBassadorTest$SubTestEvent.class | Bin 0 -> 426 bytes .../org/mbassy/MBassadorTest$TestEvent.class | Bin 0 -> 520 bytes .../org/mbassy/MBassadorTest.class | Bin 0 -> 4100 bytes 39 files changed, 1163 insertions(+) create mode 100644 mbassador.iml create mode 100644 pom.xml create mode 100644 src/main/java/org/mbassy/Listener.java create mode 100644 src/main/java/org/mbassy/MBassador.java create mode 100644 src/main/java/org/mbassy/common/ConcurrentLinkedBag.java create mode 100644 src/main/java/org/mbassy/common/IPredicate.java create mode 100644 src/main/java/org/mbassy/common/IPublicationErrorHandler.java create mode 100644 src/main/java/org/mbassy/common/PublicationError.java create mode 100644 src/main/java/org/mbassy/common/ReflectionUtils.java create mode 100644 src/main/java/org/mbassy/filter/Filter.java create mode 100644 src/main/java/org/mbassy/filter/MessageFilter.java create mode 100644 src/test/java/org/mbassy/ConcurrentExecutor.java create mode 100644 src/test/java/org/mbassy/MBassadorTest.java create mode 100644 target/classes/org/mbassy/Listener$Dispatch.class create mode 100644 target/classes/org/mbassy/Listener.class create mode 100644 target/classes/org/mbassy/MBassador$1.class create mode 100644 target/classes/org/mbassy/MBassador$2.class create mode 100644 target/classes/org/mbassy/MBassador$ConsoleLogger.class create mode 100644 target/classes/org/mbassy/MBassador$Subscription$1.class create mode 100644 target/classes/org/mbassy/MBassador$Subscription.class create mode 100644 target/classes/org/mbassy/MBassador.class create mode 100644 target/classes/org/mbassy/common/ConcurrentLinkedBag$1.class create mode 100644 target/classes/org/mbassy/common/ConcurrentLinkedBag$ListEntry.class create mode 100644 target/classes/org/mbassy/common/ConcurrentLinkedBag.class create mode 100644 target/classes/org/mbassy/common/IPredicate.class create mode 100644 target/classes/org/mbassy/common/IPublicationErrorHandler.class create mode 100644 target/classes/org/mbassy/common/PublicationError.class create mode 100644 target/classes/org/mbassy/common/ReflectionUtils.class create mode 100644 target/classes/org/mbassy/filter/Filter.class create mode 100644 target/classes/org/mbassy/filter/MessageFilter$All.class create mode 100644 target/classes/org/mbassy/filter/MessageFilter.class create mode 100644 target/test-classes/org/mbassy/ConcurrentExecutor$1.class create mode 100644 target/test-classes/org/mbassy/ConcurrentExecutor.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest$1.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest$2.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest$EventingTestBean.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest$SubTestEvent.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest$TestEvent.class create mode 100644 target/test-classes/org/mbassy/MBassadorTest.class diff --git a/mbassador.iml b/mbassador.iml new file mode 100644 index 0000000..f6a4b36 --- /dev/null +++ b/mbassador.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8b6e0bd --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + org.mbassy + mbassador + 1.0.0.RC + jar + mbassador + Library for simple implementation of bidirectional conversions + + + UTF-8 + 1.6 + + + + + + junit + junit + 4.10 + compile + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${project.build.java.version} + ${project.build.java.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/src/main/java/org/mbassy/Listener.java b/src/main/java/org/mbassy/Listener.java new file mode 100644 index 0000000..b5f2985 --- /dev/null +++ b/src/main/java/org/mbassy/Listener.java @@ -0,0 +1,27 @@ +package org.mbassy; + +import org.mbassy.filter.Filter; + +import java.lang.annotation.*; + +/** + * TODO. Insert class description here + *

+ * User: benni + * Date: 2/8/12 + * Time: 3:35 PM + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Inherited +@Target(value = {ElementType.METHOD}) +public @interface Listener { + + Filter[] value() default {}; // no filters by default + + Dispatch mode() default Dispatch.Synchronous; + + public static enum Dispatch{ + Synchronous,Asynchronous + } + +} diff --git a/src/main/java/org/mbassy/MBassador.java b/src/main/java/org/mbassy/MBassador.java new file mode 100644 index 0000000..3be67bc --- /dev/null +++ b/src/main/java/org/mbassy/MBassador.java @@ -0,0 +1,427 @@ +package org.mbassy; + +import org.mbassy.filter.Filter; +import org.mbassy.filter.MessageFilter; +import org.mbassy.common.*; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.*; + +/** + * + * A message bus offers facilities for publishing messages to registered listeners. Messages can be dispatched + * synchronously or asynchronously and may be of any type that is a valid sub type of the type parameter T. + * The dispatch mechanism can by controlled for each concrete message publication. + * A message publication is the publication of any message using one of the bus' publish(..) methods. + *

+ * Each message publication is isolated from all other running publications such that it does not interfere with them. + * Hence, the bus expects message handlers to be stateless as it may invoke them concurrently if multiple + * messages get published asynchronously. + *

+ * A listener is any object that defines at least one message handler and that has been subscribed to at least + * one message bus. A message handler can be any method that accepts exactly one parameter (the message) and is marked + * as a message handler using the @Listener annotation. + *

+ * The bus uses weak references to all listeners such that registered listeners do not need to + * be explicitly unregistered to be eligible for garbage collection. Dead (garbage collected) listeners are + * removed on-the-fly as messages get dispatched. + *

+ * Generally message handlers will be invoked in inverse sequence of insertion (subscription) but any + * class using this bus should not rely on this assumption. The basic contract of the bus is that it will deliver + * a specific message exactly once to each of the subscribed message handlers. + *

+ * Messages are dispatched to all listeners that accept the type or supertype of the dispatched message. Additionally + * a message handler may define filters to narrow the set of messages that it accepts. + *

+ * Subscribed message handlers are available to all pending message publications that have not yet started processing. + * Any messageHandler may only be subscribed once (subsequent subscriptions of an already subscribed messageHandler will be silently ignored) + *

+ * Removing a listener means removing all subscribed message handlers of that object. This remove operation + * immediately takes effect and on all running dispatch processes. A removed listener (a listener + * is considered removed after the remove(Object) call returned) will under no circumstances receive any message publications. + * + * NOTE: Generic type parameters of messages will not be taken into account, e.g. a List will + * get dispatched to all message handlers that take an instance of List as their parameter + * + * @Author bennidi + * Date: 2/8/12 + */ +public class MBassador{ + + + // This predicate is used to find all message listeners (methods annotated with @Listener) + private static final IPredicate AllMessageListeners = new IPredicate() { + @Override + public boolean apply(Method target) { + return target.getAnnotation(Listener.class) != null; + } + }; + + // This is the default error handler it will simply log to standard out and + // print stack trace if available + protected static final class ConsoleLogger implements IPublicationErrorHandler { + @Override + public void handleError(PublicationError error) { + System.out.println(error); + if (error.getCause() != null) error.getCause().printStackTrace(); + } + }; + + // executor for asynchronous listeners using unbound queuing strategy to ensure that no events get lost + private ExecutorService executor = new ThreadPoolExecutor(5, 50, 1, TimeUnit.MINUTES, new LinkedBlockingQueue()); + + // cache already created filter instances + private final Map, MessageFilter> filterCache = new HashMap, MessageFilter>(); + + // all subscriptions per message type + // this is the primary list for dispatching a specific message + private final Map> subscriptionsPerMessage = new HashMap(50); + + // all subscriptions per messageHandler type + // this list provides access for subscribing and unsubsribing + private final Map> subscriptionsPerListener = new HashMap(50); + + // remember already processed classes that do not contain any listeners + private final Collection nonListeners = new HashSet(); + + // this handler will receive all errors that occur during message dispatch or message handling + private IPublicationErrorHandler errorHandler = new ConsoleLogger(); + + + // all threads that are available for asynchronous message dispatching + private final CopyOnWriteArrayList dispatchers = new CopyOnWriteArrayList(); + + // all pending messages scheduled for asynchronous dispatch are queued here + private final LinkedBlockingQueue pendingMessages = new LinkedBlockingQueue(); + + // initialize the dispatch workers + private void initDispatcherThreads(int numberOfThreads) { + for (int i = 0; i < numberOfThreads; i++) { + // each thread will run forever and process incoming + //dispatch requests + Thread dispatcher = new Thread(new Runnable() { + public void run() { + while (true) { + try { + publish(pendingMessages.take()); + } catch (InterruptedException e) { + errorHandler.handleError(new PublicationError(e, "Asnchronous publication interupted", null, null, null)); + return; + } + } + } + }); + dispatchers.add(dispatcher); + dispatcher.start(); + } + } + + public MBassador(){ + initDispatcherThreads(2); + } + + public MBassador(int dispatcherThreadCount){ + initDispatcherThreads(dispatcherThreadCount > 0 ? dispatcherThreadCount : 2); + } + + + public void publishAsync(T message){ + pendingMessages.offer(message); + } + + + /** + * Synchronously publish a message to all registered listeners (this includes listeners defined for super types) + * The call blocks until every messageHandler has processed the message. + * + * @param message + */ + public void publish(T message){ + try { + final Collection subscriptions = getSubscriptionsByMessageType(message.getClass()); + for (Subscription subscription : subscriptions) subscription.publish(message); + } catch (Throwable e) { + handlePublicationError(new PublicationError() + .setMessage("Error during publication of message") + .setCause(e) + .setPublishedObject(message)); + } + + } + + /** + * Immediately unsubscribe all registered message handlers (if any) of the given listener. When this call returns + * have effectively been removed and will not receive any message publications (including asynchronously scheduled + * publications that have been published when the messageHandler was still subscribed). + * A call to this method passing null, an already subscribed message or any message that does not define any listeners + * will not have any effect. + * + * @param listener + */ + public void unsubscribe(Object listener){ + if (listener == null) return; + Collection subscriptions = subscriptionsPerListener.get(listener.getClass()); + for (Subscription subscription : subscriptions) { + subscription.unsubscribe(listener); + } + } + + + /** + * Subscribe all listeners of the given message to receive message publications. + * Any message may only be subscribed once (subsequent subscriptions of an already subscribed + * message will be silently ignored) + * + * @param listener + */ + public void subscribe(Object listener){ + Class listeningClass = listener.getClass(); + if (nonListeners.contains(listeningClass)) + return; // early reject of known classes that do not participate in eventing + Collection subscriptionsByListener = subscriptionsPerListener.get(listeningClass); + if (subscriptionsByListener == null) { // if the type is registered for the first time + synchronized (this) { // new subscriptions must be processed sequentially for each class + subscriptionsByListener = subscriptionsPerListener.get(listeningClass); + if (subscriptionsByListener == null) { // double check (a bit ugly but works here) + List messageHandlers = getListeners(listeningClass); // get all methods with subscriptions + subscriptionsByListener = new ArrayList(messageHandlers.size()); // it's safe to use non-concurrent collection here (read only) + if (messageHandlers.isEmpty()) { // remember the class as non listening class + nonListeners.add(listeningClass); + return; + } + // create subscriptions for all detected listeners + for (Method messageHandler : messageHandlers) { + if (!isValidMessageHandler(messageHandler)) continue; // ignore invalid listeners + MessageFilter[] filter = getFilter(messageHandler.getAnnotation(Listener.class)); + Class eventType = getMessageType(messageHandler); + Subscription subscription = new Subscription(messageHandler, filter); + subscription.subscribe(listener); + addMessageTypeSubscription(eventType, subscription); + subscriptionsByListener.add(subscription); + //updateMessageTypeHierarchy(eventType); + } + subscriptionsPerListener.put(listeningClass, subscriptionsByListener); + } + } + } + // register the message to the existing subscriptions + for (Subscription sub : subscriptionsByListener) sub.subscribe(listener); + } + + + public void setErrorHandler(IPublicationErrorHandler handler){ + this.errorHandler = handler; + } + + + + // obtain the set of subscriptions for the given message type + private Collection getSubscriptionsByMessageType(Class messageType) { + // TODO improve with cache + Collection subscriptions = new LinkedList(); + + if(subscriptionsPerMessage.get(messageType) != null) { + subscriptions.addAll(subscriptionsPerMessage.get(messageType)); + } + for (Class eventSuperType : getSuperclasses(messageType)){ + if(subscriptionsPerMessage.get(eventSuperType) != null){ + subscriptions.addAll(subscriptionsPerMessage.get(eventSuperType)); + } + } + + return subscriptions; + } + + private Collection getSuperclasses(Class from){ + Collection superclasses = new LinkedList(); + while(!from.equals(Object.class)){ + superclasses.add(from.getSuperclass()); + from = from.getSuperclass(); + } + return superclasses; + } + + // associate a suscription with a message type + private void addMessageTypeSubscription(Class messageType, Subscription subscription) { + Collection subscriptions = subscriptionsPerMessage.get(messageType); + if (subscriptions == null) { + subscriptions = new CopyOnWriteArraySet(); + subscriptionsPerMessage.put(messageType, subscriptions); + } + subscriptions.add(subscription); + } + + /* + private void updateMessageTypeHierarchy(Class messageType) { + for (Class existingEventType : subscriptionsPerMessage.keySet()) { + if (existingEventType.equals(messageType)) continue; + if (messageType.isAssignableFrom(existingEventType)) //message is super type of existing + messageTypeHierarchy.put(existingEventType, messageType); + else if (existingEventType.isAssignableFrom(messageType)) { // message is sub type of existing + messageTypeHierarchy.put(messageType, existingEventType); // add direct super type + messageTypeHierarchy.putAll(messageType, messageTypeHierarchy.get(existingEventType)); // add all super types of super type + } + } + }*/ + + + private boolean isValidMessageHandler(Method handler) { + if (handler.getParameterTypes().length != 1) { + // a messageHandler only defines one parameter (the message) + System.out.println("Found nono or more than one parameter in messageHandler [" + handler.getName() + + "]. A messageHandler must define exactly one parameter"); + return false; + } + return true; + } + + private static Class getMessageType(Method listener) { + return listener.getParameterTypes()[0]; + } + + // get all listeners defined by the given class (includes + // listeners defined in super classes) + private static List getListeners(Class target) { + return ReflectionUtils.getMethods(AllMessageListeners, target); + } + + // retrieve all instances of filters associated with the given subscription + private MessageFilter[] getFilter(Listener subscription) { + if (subscription.value().length == 0) return null; + MessageFilter[] filters = new MessageFilter[subscription.value().length]; + int i = 0; + for (Filter filterDef : subscription.value()) { + MessageFilter filter = filterCache.get(filterDef.value()); + if (filter == null) { + try { + filter = filterDef.value().newInstance(); + filterCache.put(filterDef.value(), filter); + } catch (Throwable e) { + handlePublicationError(new PublicationError() + .setMessage("Error retrieving filter")); + } + + } + filters[i] = filter; + i++; + } + return filters; + } + + + + private void handlePublicationError(PublicationError error) { + errorHandler.handleError(error); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + for(Thread dispatcher : dispatchers){ + dispatcher.interrupt(); + } + } + + /** + * Subscription is a thread safe container for objects that contain message handlers + * + */ + private class Subscription { + + private final MessageFilter[] filter; + + private final Method messageHandler; + + private ConcurrentLinkedBag listeners = new ConcurrentLinkedBag(); + + private boolean isAynchronous; + + private Subscription(Method messageHandler, MessageFilter[] filter) { + this.messageHandler = messageHandler; + this.filter = filter; + this.messageHandler.setAccessible(true); + this.isAynchronous = messageHandler.getAnnotation(Listener.class).mode().equals(Listener.Dispatch.Asynchronous); + } + + + public void subscribe(Object o) { + listeners.add(o); + + } + + private void dispatch(final Object message, final Object listener){ + if(isAynchronous){ + MBassador.this.executor.execute(new Runnable() { + @Override + public void run() { + invokeHandler(message, listener); + } + }); + } + else{ + invokeHandler(message, listener); + } + } + + private void invokeHandler(final Object message, final Object listener){ + try { + messageHandler.invoke(listener, message); + }catch(IllegalAccessException e){ + MBassador.this.handlePublicationError( + new PublicationError(e, "Error during messageHandler notification. " + + "The class or method is not accessible", + messageHandler, listener, message)); + } + catch(IllegalArgumentException e){ + MBassador.this.handlePublicationError( + new PublicationError(e, "Error during messageHandler notification. " + + "Wrong arguments passed to method. Was: " + message.getClass() + + "Expected: " + messageHandler.getParameterTypes()[0], + messageHandler, listener, message)); + } + catch (InvocationTargetException e) { + MBassador.this.handlePublicationError( + new PublicationError(e, "Error during messageHandler notification. " + + "Message handler threw exception", + messageHandler, listener, message)); + } + catch (Throwable e) { + MBassador.this.handlePublicationError( + new PublicationError(e, "Error during messageHandler notification. " + + "Unexpected exception", + messageHandler, listener, message)); + } + } + + public void publish(Object message) { + + Iterator iterator = listeners.iterator(); + Object listener = null; + while ((listener = iterator.next()) != null) { + if(passesFilter(message, listener)) { + dispatch(message, listener); + } + } + } + + private boolean passesFilter(Object message, Object listener) { + + if (filter == null) { + return true; + } + else { + for (int i = 0; i < filter.length; i++) { + if (!filter[i].accepts(message, listener)) return false; + } + return true; + } + } + + public void unsubscribe(Object existingListener) { + listeners.remove(existingListener); + } + } + +} diff --git a/src/main/java/org/mbassy/common/ConcurrentLinkedBag.java b/src/main/java/org/mbassy/common/ConcurrentLinkedBag.java new file mode 100644 index 0000000..1483ca7 --- /dev/null +++ b/src/main/java/org/mbassy/common/ConcurrentLinkedBag.java @@ -0,0 +1,159 @@ +package org.mbassy.common; + + +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.WeakHashMap; + +/** + * 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. + * + * The structure uses weak references to the elements. Iterators automatically perform cleanups of + * garbace collect objects during iteration. + * No dedicated maintenance operations need to be called or run in background. + * + * + *

+ * @author bennidi + * Date: 2/12/12 + */ +public class ConcurrentLinkedBag implements Iterable { + + + private WeakHashMap> entries = new WeakHashMap>(); // maintain a map of entries for O(log n) lookup + + private ListEntry head; // reference to the first element + + public ConcurrentLinkedBag add(T element) { + if (element == null || entries.containsKey(element)) return this; + synchronized (this) { + insert(element); + } + return this; + } + + private void insert(T element) { + if(head == null){ + head = new ListEntry(element); + } + else{ + head = new ListEntry(element, head); + } + entries.put(element, head); + } + + public ConcurrentLinkedBag addAll(Iterable elements) { + for (T element : elements) { + if (element == null || entries.containsKey(element)) return this; + synchronized (this) { + insert(element); + } + } + return this; + } + + public ConcurrentLinkedBag remove(T element) { + if (!entries.containsKey(element)) return this; + synchronized (this) { + ListEntry listelement = entries.get(element); + if(listelement != head){ + listelement.remove(); + } + else{ + head = head.next(); + } + entries.remove(element); + } + return this; + } + + public Iterator iterator() { + return new Iterator() { + + private ListEntry current = head; + + public boolean hasNext() { + if(current == null) return false; + T value = current.getValue(); + if(value == null){ // auto-removal of orphan references + remove(); + return hasNext(); + } + else{ + return true; + } + } + + public T next() { + if(current == null) return null; + T value = current.getValue(); + if(value == null){ // auto-removal of orphan references + remove(); + return next(); + } + else{ + current = current.next(); + return value; + } + } + + public void remove() { + if(current == null)return; + synchronized (ConcurrentLinkedBag.this){ + current.remove(); + current = current.next();} + } + }; + } + + + public class ListEntry { + + private WeakReference value; + + private ListEntry next; + + private ListEntry predecessor; + + + private ListEntry(T value) { + this.value = new WeakReference(value); + } + + private ListEntry(T value, ListEntry next) { + this(value); + this.next = next; + next.predecessor = this; + } + + public T getValue() { + return value.get(); + } + + public void remove(){ + if(predecessor != null){ + predecessor.setNext(next()); + } + else if(next() != null){ + next.predecessor = null; + } + } + + public void setNext(ListEntry element) { + this.next = element; + if(element != null)element.predecessor = this; + } + + public ListEntry next() { + return next; + } + + public boolean hasNext() { + return next() != null; + } + + } +} diff --git a/src/main/java/org/mbassy/common/IPredicate.java b/src/main/java/org/mbassy/common/IPredicate.java new file mode 100644 index 0000000..7598643 --- /dev/null +++ b/src/main/java/org/mbassy/common/IPredicate.java @@ -0,0 +1,14 @@ +package org.mbassy.common; + +/** + * Created with IntelliJ IDEA. + * User: benni + * Date: 10/22/12 + * Time: 9:33 AM + * To change this template use File | Settings | File Templates. + */ +public interface IPredicate { + + public boolean apply(T target); + +} diff --git a/src/main/java/org/mbassy/common/IPublicationErrorHandler.java b/src/main/java/org/mbassy/common/IPublicationErrorHandler.java new file mode 100644 index 0000000..1789961 --- /dev/null +++ b/src/main/java/org/mbassy/common/IPublicationErrorHandler.java @@ -0,0 +1,13 @@ +package org.mbassy.common; + +/** + * TODO. Insert class description here + *

+ * User: benni + * Date: 2/22/12 + * Time: 5:03 PM + */ +public interface IPublicationErrorHandler { + + public void handleError(PublicationError error); +} diff --git a/src/main/java/org/mbassy/common/PublicationError.java b/src/main/java/org/mbassy/common/PublicationError.java new file mode 100644 index 0000000..e117571 --- /dev/null +++ b/src/main/java/org/mbassy/common/PublicationError.java @@ -0,0 +1,98 @@ +package org.mbassy.common; + +import java.lang.reflect.Method; + +/** + * Publication errors are created when object publication fails for some reason and contain details + * as to the cause and location where they occured. + *

+ * User: benni + * Date: 2/22/12 + * Time: 4:59 PM + */ +public class PublicationError { + + private Throwable cause; + + private String message; + + private Method listener; + + private Object listeningObject; + + private Object publishedObject; + + + public PublicationError(Throwable cause, String message, Method listener, Object listeningObject, Object publishedObject) { + this.cause = cause; + this.message = message; + this.listener = listener; + this.listeningObject = listeningObject; + this.publishedObject = publishedObject; + } + + public PublicationError(){ + super(); + } + + public Throwable getCause() { + return cause; + } + + public PublicationError setCause(Throwable cause) { + this.cause = cause; + return this; + } + + public String getMessage() { + return message; + } + + public PublicationError setMessage(String message) { + this.message = message; + return this; + } + + public Method getListener() { + return listener; + } + + public PublicationError setListener(Method listener) { + this.listener = listener; + return this; + } + + public Object getListeningObject() { + return listeningObject; + } + + public PublicationError setListeningObject(Object listeningObject) { + this.listeningObject = listeningObject; + return this; + } + + public Object getPublishedObject() { + return publishedObject; + } + + public PublicationError setPublishedObject(Object publishedObject) { + this.publishedObject = publishedObject; + return this; + } + + @Override + public String toString() { + return "PublicationError{" + + "\n" + + "\tcause=" + cause + + "\n" + + "\tmessage='" + message + '\'' + + "\n" + + "\tlistener=" + listener + + "\n" + + "\tlisteningObject=" + listeningObject + + "\n" + + "\tpublishedObject=" + publishedObject + + '}'; + } +} diff --git a/src/main/java/org/mbassy/common/ReflectionUtils.java b/src/main/java/org/mbassy/common/ReflectionUtils.java new file mode 100644 index 0000000..b61c764 --- /dev/null +++ b/src/main/java/org/mbassy/common/ReflectionUtils.java @@ -0,0 +1,70 @@ +package org.mbassy.common; + +import com.google.common.base.Predicate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +/** + * User: benni + * Date: 2/16/12 + * Time: 12:14 PM + */ +public class ReflectionUtils { + + private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); + + public static List getMethods(IPredicate condition, Class target) { + List methods = new LinkedList(); + try { + for (Method method : target.getDeclaredMethods()) { + if (condition.apply(method)) { + methods.add(method); + } + } + } catch (Exception e) { + //nop + } + if (!target.equals(Object.class)) { + methods.addAll(getMethods(condition, target.getSuperclass())); + } + return methods; + } + + public static List getFields(Predicate condition, Class target) { + List methods = new LinkedList(); + try { + for (Field method : target.getDeclaredFields()) { + if (condition.apply(method)) { + methods.add(method); + } + } + } catch (Exception e) { + //nop + } + if (!target.equals(Object.class)) { + methods.addAll(getFields(condition, target.getSuperclass())); + } + return methods; + } + + public static Object callMethod(Object o, final String methodName, Object... args) { + + if(o == null || methodName == null) { + return null; + } + + Object res = null; + try { + Method m = o.getClass().getMethod(methodName); + res = m.invoke(o, args); + } catch (Exception e) { + //logger.warn("Not possible to get value", e); + } + return res; + } +} diff --git a/src/main/java/org/mbassy/filter/Filter.java b/src/main/java/org/mbassy/filter/Filter.java new file mode 100644 index 0000000..6da4e42 --- /dev/null +++ b/src/main/java/org/mbassy/filter/Filter.java @@ -0,0 +1,23 @@ +package org.mbassy.filter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THe filter annotation is used to add filters to message listeners. + * It references a class that implements the MessageFilter interface. + * The object filter will be used to check whether a message should be delivered + * to the message listener or not. + * + *

+ * @author benni + * Date: 2/14/12 + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {ElementType.ANNOTATION_TYPE}) +public @interface Filter { + + Class value(); +} diff --git a/src/main/java/org/mbassy/filter/MessageFilter.java b/src/main/java/org/mbassy/filter/MessageFilter.java new file mode 100644 index 0000000..0169ccf --- /dev/null +++ b/src/main/java/org/mbassy/filter/MessageFilter.java @@ -0,0 +1,31 @@ +package org.mbassy.filter; + +/** + * Object filters can be used to prevent certain messages to be delivered to a specific listener. + * If a filter is used the message will only be delivered if it passes the filter(s) + * + * User: benni + * Date: 2/8/12 + */ +public interface MessageFilter { + + /** + * Evaluate the message and listener to ensure that the message should be handled by the listener + * + * + * @param event the event to be delivered + * @param listener the listener instance that would receive the event if it passes the filter + * @return + */ + public boolean accepts(Object event, Object listener); + + + public static final class All implements MessageFilter { + + @Override + public boolean accepts(Object event, Object listener) { + return true; + } + } + +} diff --git a/src/test/java/org/mbassy/ConcurrentExecutor.java b/src/test/java/org/mbassy/ConcurrentExecutor.java new file mode 100644 index 0000000..1bf34f3 --- /dev/null +++ b/src/test/java/org/mbassy/ConcurrentExecutor.java @@ -0,0 +1,68 @@ +package org.mbassy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +/** + * Run various tests concurrently. A given instance of runnable will be used to spawn and start + * as many threads as specified by an additional parameter or (if multiple runnables have been + * passed to the method) one thread for each runnable. + *

+ * Date: 2/14/12 + * + * @Author bennidi + */ +public class ConcurrentExecutor { + + + public static void runConcurrent(final Runnable unit, int numberOfConcurrentExecutions) { + Runnable[] units = new Runnable[numberOfConcurrentExecutions]; + // create the tasks and schedule for execution + for (int i = 0; i < numberOfConcurrentExecutions; i++) { + units[i] = unit; + } + runConcurrent(units); + + } + + public static void runConcurrent(final Runnable... units) { + ExecutorService executor = Executors.newCachedThreadPool(); + List> returnValues = new ArrayList>(); + + // create the tasks and schedule for execution + for (final Runnable unit : units) { + Callable wrapper = new Callable() { + @Override + public Long call() throws Exception { + long start = System.currentTimeMillis(); + unit.run(); + return System.currentTimeMillis() - start; + } + }; + returnValues.add(executor.submit(wrapper)); + } + + // wait until all tasks have been executed + try { + executor.shutdown();// tells the thread pool to execute all waiting tasks + executor.awaitTermination(5, TimeUnit.MINUTES); + } catch (InterruptedException e) { + // unlikely that this will happen + e.printStackTrace(); + } + + // print results + for (Future result : returnValues) + try { + System.out.println("Execution of unit of work to " + result.get() + "ms."); + } catch (Exception e) { + //should never happen + // since the code waits until all tasks are processed + e.printStackTrace(); + } + + } + + +} diff --git a/src/test/java/org/mbassy/MBassadorTest.java b/src/test/java/org/mbassy/MBassadorTest.java new file mode 100644 index 0000000..6103e41 --- /dev/null +++ b/src/test/java/org/mbassy/MBassadorTest.java @@ -0,0 +1,167 @@ +package org.mbassy; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Test synchronous and asynchronous dispatch in single and multi-threaded scenario. + * + * @author bennidi + * Date: 2/8/12 + */ +public class MBassadorTest { + + + @Test + public void testAsynchronous() throws InterruptedException { + + MBassador bus = new MBassador(); + int listenerCount = 1000; + List persistentReferences = new ArrayList(); + + for (int i = 1; i <= listenerCount; i++) { + EventingTestBean bean = new EventingTestBean(); + persistentReferences.add(bean); + bus.subscribe(bean); + } + + TestEvent event = new TestEvent(); + TestEvent subEvent = new SubTestEvent(); + + bus.publishAsync(event); + bus.publishAsync(subEvent); + + Thread.sleep(2000); + + Assert.assertTrue(event.counter.get() == 1000); + Assert.assertTrue(subEvent.counter.get() == 1000 * 2); + + } + + @Test + public void testSynchronous() throws InterruptedException { + + MBassador bus = new MBassador(); + int listenerCount = 100; + List persistentReferences = new ArrayList(); + for (int i = 1; i <= listenerCount; i++) { + + + EventingTestBean bean = new EventingTestBean(); + persistentReferences.add(bean); + bus.subscribe(bean); + + TestEvent event = new TestEvent(); + TestEvent subEvent = new SubTestEvent(); + + bus.publish(event); + bus.publish(subEvent); + + Assert.assertEquals(i, event.counter.get()); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + + Assert.assertEquals(i * 2, subEvent.counter.get()); + + } + + } + + @Test + public void testConcurrentPublication() { + final MBassador bus = new MBassador(); + final int listenerCount = 100; + final int concurenny = 20; + final CopyOnWriteArrayList testEvents = new CopyOnWriteArrayList(); + final CopyOnWriteArrayList subtestEvents = new CopyOnWriteArrayList(); + final CopyOnWriteArrayList persistentReferences = new CopyOnWriteArrayList(); + + ConcurrentExecutor.runConcurrent(new Runnable() { + @Override + public void run() { + long start = System.currentTimeMillis(); + for (int i = 0; i < listenerCount; i++) { + EventingTestBean bean = new EventingTestBean(); + persistentReferences.add(bean); + bus.subscribe(bean); + } + + long end = System.currentTimeMillis(); + System.out.println("MBassador: Creating " + listenerCount + " listeners took " + (end - start) + " ms"); + } + }, concurenny); + + ConcurrentExecutor.runConcurrent(new Runnable() { + @Override + public void run() { + long start = System.currentTimeMillis(); + for (int i = 0; i < listenerCount; i++) { + TestEvent event = new TestEvent(); + SubTestEvent subEvent = new SubTestEvent(); + testEvents.add(event); + subtestEvents.add(subEvent); + + bus.publishAsync(event); + bus.publish(subEvent); + } + + long end = System.currentTimeMillis(); + System.out.println("MBassador: Publishing " + 2 * listenerCount + " events took " + (end - start) + " ms"); + } + }, concurenny); + + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + + for(TestEvent event : testEvents){ + Assert.assertEquals(listenerCount * concurenny, event.counter.get()); + } + + for(SubTestEvent event : subtestEvents){ + Assert.assertEquals(listenerCount * concurenny * 2, event.counter.get()); + } + + } + + + public static class TestEvent { + + public AtomicInteger counter = new AtomicInteger(); + + } + + public static class SubTestEvent extends TestEvent { + + } + + + public class EventingTestBean { + + + @Listener + public void handleTestEvent(TestEvent event) { + event.counter.incrementAndGet(); + } + + @Listener(mode = Listener.Dispatch.Asynchronous) + public void handleSubTestEvent(SubTestEvent event) { + event.counter.incrementAndGet(); + } + + + } + + +} diff --git a/target/classes/org/mbassy/Listener$Dispatch.class b/target/classes/org/mbassy/Listener$Dispatch.class new file mode 100644 index 0000000000000000000000000000000000000000..85ee3e70065011690667ea30e3ff47af0a05cafd GIT binary patch literal 1066 zcmah|VQtpYOi_G|-Gg#1jQiQIe36P*I>_gD=lyBozXm zb4FFh65neQHW?DV6I&m-j_pi52KklgebRh=#Gvlkw&Ctrn&%mWtaTlCSQ`&0JgId} z&o>AyUea5{-}YuJNyYwl_uX!fVg3EQ)Y!3RO}I#*RGv!~cbpFf!(!L8jW?6=z;NGc z1B*_ipvu8VhDND-q#bKDOS6Zyp6{CWuvMOCBHeX#&FX8e$;*W`MOzzF>Gc2g7-DVH zHvKJzyEn+~mHP~$KQgJSc+VW#nm=)=l2GD++}5oyd2^=pu00vIZ}+)HlU91p#MO=0 zCht0Vc^6gQvWhgn`HYG>8WLVGTL_)(Qz!hZ{(yLxmV`6N zr-%oEA_hSs`ja4GO3&y}oYMdG6pX7~bf=IVP-i)^E`$n?P^7a69%GSqg_t7BSm{-|s$d*+!kjCu^ELF9p@Ya)2M3F#dD7}~j z4$n)iS`c<^zs;GE{i-}ep?&v#T?wq;XdM`7RG`cBDr3(Gr2U}7rN#5obY)FNhPcJ) zq$gJ&{YO}Q8fmu|20Dn@RPHH=LzlQY%P&s^FZyyQ1q#hr8|BlBid5U9#1|J9Yh#Y> zN!_*OuVK4M>}YJAt(^Tsc?>+|j+kgQpC6oRz4E7ORre@VhB{0>oUQ7}uuF#XC$sYv zk9>CKj0VJ{`Fh2(Z zxirs=EJq6*rCI<3IV}DF8Nw2lnYl9NVijwAA9K9U1dFhNJT@8H8iy?i!Zvm=&q$7u JG@s0M;S1N!y<`9Y literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/MBassador$1.class b/target/classes/org/mbassy/MBassador$1.class new file mode 100644 index 0000000000000000000000000000000000000000..87921e352cdd05e3fe472455160c5b90b04cdbc8 GIT binary patch literal 959 zcmZ`%O>Yx16dWg;Y!kL2G;JuQrF;a)hs3rB35k@7Qh`)yLy>}d;O1;h>aww;jf3bf z;=+YXkvJeu9QaX)x0_Y65OJ`d{XB2%8NXk@zkLVr5UnC?+_Z742n)Au+$kcDnvJ_Q z?%BA{kl&L^ntg_By>Y@|9qPVdxa>(KUQLEQ5x(KQfHEtd_IYr^LmBOpq-9R!m;nsV zky0W&4ET5~D6{VAaNrJmv^{q_&nfV}4r{w@h8!P_f^&wgx_8Dua5vy;;D+K|AbjI? zggMpycH=EQ%7tG^eG2QX~T`wis4b(@wfrnjg&2X26g9EZR}7R+SE5o)TLP(q?>k^XysC*mO`%i z73>QwGH__l$H~W7rnNH#poA;5qklz(=BW6X&>4#5Rt1?0WPif8KEcXrT`_6vzJq3x!gy0s$3V5oKAF0^S8tu1d0a!;tu~+a1}m-I>j_Re2R(z|SNK zUJ@U`m+=kMGu;)6EQTiS%sDf2&hK10KYo4v4qyh4T47<#L_0DjE@Rw87881Q)xb49 zP8zUVFlC|z(`|I%x`7)8ZW_47&>R-MV04P1KPT0eUCui}uw$<;(%=-Nnx!3cp76qX zhQUnzE#sREsU=z944pYo@F!6@&(%vO?~|q{Ctb(ibd;y%nlh!GGJwHa6N0NHpWJcM z4Ak{7Izt%5mo&6f`D?8HnMr-7QAL8j8BNFtCY{@M-Z-}Jh%I|fgj}hp67s^zJC|2N zPYOEHz7aZZY2B&BsFXkkb3;bT<*S~Kr{f?sliC)Aydqp*2AuA~)rKF-w3xRNp zN(vbT>B`}9+S3un0cwmT1Irdxuu7>+m^}z*bcUhpkm_lEo4X;^?@;w35~5nv-g*WY z`gEf#dqKqs-6C~BQE{h0RZV5qbOlVub$LL|Hbw2Wcv3S}OB98kZ@W^sky2cQHuY7B z7Z!c#mMGKDBOY-oRwjl!#a?0vof4I=H#45-fohOs$cXb66e)(x@%1G9w1!_k5k8Ta zTEW)-t=MBn6nw7#tYXp^WlO%*>77jgY=nx!O==W>?NedwYO^I*YQZE<*EAYGdwMX2 zwqlG}>#wmR!Jg2Dv*Vjc(>2si|8xgfSC{U7x}6&6?M4r2&(Lg@WUVo?6Q97c`)JrD zsS&;OZKgfI7W(LG#d8z=7$oWJ5zRT07|vs;X7fGWYTBlq{YWP1{fx#vr1sGC;rGv6 zRbhNX%d3f=R>s&q%r9tbz;5iKn_M>|6$A96iOiG;SCZ@#NEwq5F;G9b=p&F}T!_!v n$I!G!qowIJX!nMJixk-ii4C}fQ5sWp6<@&s%}g?4(MZo9dXV`}pBbe%9>eU2%V8|`wN}Kj*({<0 zl_W~~$N3PZ3LyGS#U(9~2;EcLEsj+B3_BboIwEqFpLUHV6u!8tdNVoA8>7+8>GTUxB< znocs@2u0T6c}i_tZMF(T{gGCm&6Z3RSpI34*2^legXn7>5W{kz57QZ72Q^w@`W5YS z2^BL9@@C2N$uhFl_BXIEHto?_v%%Mxqq{eLfO*^`k2V-fbQjp)7c>jZ&Am-WnCE~C#=MQWy&$K2F2=>UZraZx3Ngp=&fNL3t0OD Dpwi=( literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/MBassador$Subscription$1.class b/target/classes/org/mbassy/MBassador$Subscription$1.class new file mode 100644 index 0000000000000000000000000000000000000000..966c5c65e02b6a786bb998c007d44896129f2081 GIT binary patch literal 987 zcma)5ZBNrs7(I8LD~!U*oWvJYkRqeN#`L4c4-!o@LyZ`|-*$Doq-&SHknpoiwrKp| z5Aa7B&s~%kYciVjc{zQ0`kd#v{rT(r4**Zlo<{}i+C0)`qY4vF6^qz3@z}(ciKf89 zsSKTAkfyR92-LeL@>I4%8TH%my_3Mto(WjGBvfe@L_s1@$p$KQIwo8`I?BD&Pt+(= zam4gZ#FB$hrfHB0Y;@zK-yV8Q&)SDC>0~cXoXhW!zVci}DtjUDwt0>EVuM9w`&giS z5cepk)>TpPE+2Y9@=1E(RATJQ@K`2F=YPPm)+7Ld&DlLT9d^Cay+U&~`b%{Uf|e#Q zlAIGWn!4HQQJg1!@LK5xm(C~E(#xQRSCJpasfzlCK{kkc7Iv^}!GdjJ9#snq+AL$$ z!XD}z`^>339Rd5_O>_Gf(8oNA^n6UT1lDH~3e5FXI+B?`5ZG!KS5ic#x~1>)=s8dK z_XQgN`wG^0a!u}K4u)-Oxd1&CPd#VAqh8{@j5@1sGWx*0Oy6*KCJ^rK1SNNG0>k}^ z@)zO;8nhKwL-@GG*wQi472Kx3a|OA}OyC|?&)JP>we5~cJ|_1V#y8x!V6N(Z#Ann* l{}Q`hVAqIdH*lY}ga>#?U*`Y0h8m+fu|;fSiN3;|*Kds|=0N}e literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/MBassador$Subscription.class b/target/classes/org/mbassy/MBassador$Subscription.class new file mode 100644 index 0000000000000000000000000000000000000000..02d43e41c5f6a7c44c032cf1b8c600abcc7b776d GIT binary patch literal 5232 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 Xsf;;^TTB<6QORf$eaxzxIzR6L`S7wz literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/common/ConcurrentLinkedBag$ListEntry.class b/target/classes/org/mbassy/common/ConcurrentLinkedBag$ListEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..70f231f5e320af7cb5192b1e2771af38d520233c GIT binary patch literal 2940 zcmcImTW=dx5dMxYStoH4Cp1o>6xxzd$7$l`f`B>=P16<%ZVHVR1aBMXG<9RI!+H}X z6p4$(0|_7he^>+X> zDE6Qaiz2ReQ!MqMAJ;5g??D_lMDwbM*F@YD-RoldhKPcNWec|z;yZR_TPvI{+_!h^ zT*Y=aa<0Ccdt2L^cl2HDYNw?03f(K^4afGjUD}@eH{1DQF~2~En4@<+`W4D`@4Dl; zdkV>$j-%bBie0bky25m!=5FMwYh3Q-O0{aW=H!-YPHEdEs8=XEn|l4Ky)oL1z^M6f zK7JBYO~*z~y<4tc*Ck!A*Ib1z?_Rk+dQstgxS)jZnlC$LZ$V)`6Vj)Rs@rS#b;-+* zuacal+B%6GV3B%xySk>`qPEz3Gk#w1bMYqcndrI7x+!`@cIDat-D z;S3*wSpDw_hpV)$O@g;5z~MZPZI;AHY-n%QjOU5W_;J<8GvlUAS66F00n;4d@Ba4`o3}*BaUE^cG^>!AFJ9{8 zW}#E1(6dt8c1!w3S&sS1WA3>rIo(N|!kCDslXwzO$=ig&sn)^oxSPe~tt1AJO5zEm zlQ=2j4l=TwPGS;M7FHB;k2!P-sTQiaZg^9M3&KVZXF5rcqZ#FyRu~Hv2WR<9)z4qL z7$Q<2Cw7pl2!{A&tv+76F-GBep-Xg6b6G77|f?5>L=5( zGRcUQBx9^b7{jFXJn1@*Opw+!u6QRPk^KeM0eXJoyCI{?*MI6~kTf?12>*gbSfZhWnA7V(}j?hbGLIotm{2rwFe+Bpq7CvVWzF-f( z#3;UMOJl4f4bey%7x?AO4EGb5^B#UP7Ct`rU8Qu3oLf8SPKJiu(RESeU} zGYm6}IZ9!<7=-OJgj%tOh(&+ohAGW7@rSnXeI4ON(_k--NbePu0tffS0KsNWGPsr* z+}607*zftVv&JNdO=G;obEw1(FZ<_3-Yy}}V6DO|gY3i22XGZek_kuUm7Lr9k?pkjZAp}AMZrCzFOr7aJs$*-Pv%9&V`LIr>UiN+8^PWBDInTLd|NQ6I-vCUY+=6bTLg>LQ z6(5G+!!40~B)gA8+%2j|htP~qdA&MK&b zX1-`xblBYpIN1-{MrFhJE@iU{&ev;RFBz7w@6|8yH<<$py6Vc-woxirylZv|UuMu>j>@}|V;Q-^T{0WavSnRp90SB@O5DWE`iTrTj^$v( ziwf}}J(Dqt#es>FCl%a|cq|c*0qkkUrhD{oX*xYs$DCW4GjEIPBzx&q5Lzsht&DNS zl;-PeFiOUxW;GnaQ4R0oIE$*`1k-R3?`c@ahK4gZt03-aP}PDG>!v*JXxKznLlRe* zeGNI}H59O=;agZ5W^r7OMcFY^L*Cqsi)D7i2~X+@+TZL{c5h8oAyGkRO_<)W3g5Xy ztVWToYS&aaQ^=Qew&qQPKN1aF^R@!RYt_`{)J?p%avK;=$N>KSfA>@S?RY0(zO6Fz zG+E#8mH290SBLOhBF@_ly_hoY(FlKP$f!SWAG#`?`^NZPH>f$-z@0k*_Nt$v6;CBp zD{M}{s8_OwmvD@qB|ZFR=tUF%vZ=T~#IKnq9Oi$vVfB0rD);P3J_o3g=C=tyH_#s4 zMN{+%d@H-~KSJPV%6vG%=RU5WWj_v}jqNnZljgjGAw+l@-Xn?fq+kT2^ei5Bxca#c zj{Jdr(O(c4*+K9D0?~)jm4#=~kzJ^x&$rQ>8hsvn8huFZZsI?}H;xdetbQ9u4+6HV zgVA&|;sd-LU>t&{hi}n?i$``bj1h$(tKA7Q=Pu5>liVMGzfF0KR`^tmtC-+FC@N09 zq)&&x)3nUd#HYmaBiR#AlZ-=ofi4o(yb>KzcG2<($~L-Pfm~Ar`vXt6U%#+Z6eB5mQ7k*fA>7POPvj!LF?uzy*~IW*-TectbP3c7<{lC+$}b$fN# z8bk|a30B`UIvDREMsoreh-a2uT_$Tu%;Sp9ny_$)#XiU4mv&55xk}X)M@{QkRml_0-Sdvw-a4}Z+^}bk&>>nkNs+7z=;ykh PyJk*@J{s%fnScHT;J!z) literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/common/IPredicate.class b/target/classes/org/mbassy/common/IPredicate.class new file mode 100644 index 0000000000000000000000000000000000000000..ee24e04573f74856dc1f2f482d9055b8604db66a GIT binary patch literal 239 zcmX^0Z`VEs1_mPr9(D#Ub_Q-n2G+!af}Bc522l;4ti-ZJ{hY+SbbbG%tkmQZYt1M| z2F~Ej^t{B9(xOyG1~!e55U`+zO^6kyayv{RMh33n{L-T2RJY6=paJ}z0Y#}PnaPPI zsd^yW7#U>pi_-OTlM;)IEA^A}b93|a^bu+p83fSX#Ky?L$iM{j0s{jh3nK$F0}GJP T3gj^{umLfI#m>M17U2W{!6`d^ literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/common/IPublicationErrorHandler.class b/target/classes/org/mbassy/common/IPublicationErrorHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..51ad9205caac24f2e63cbd948e999e52b5a2c644 GIT binary patch literal 211 zcmX^0Z`VEs1_mPrPId-%b_Nbc2JVc+yp)_&*P^2QB1Q&v4WIm?bp70<#Ny&g{p9@I z-26QKfYPL#%;dz9%=|pCGHcB+Mh33n{L-T2RJY8WR7M6_PjodNU{j0qvJ%S@85y)N z4aKE{kwE~YM?WVqFJ0e1DJwO(gpHAbk%0;5Yz78K7DfhU1{M&DffYzHF|Yvu+UPwg literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/common/PublicationError.class b/target/classes/org/mbassy/common/PublicationError.class new file mode 100644 index 0000000000000000000000000000000000000000..826fda4f9f1b9c7283119e7d894cb6f735b5b17f GIT binary patch literal 2526 zcmb7^>vG#v5Xb+=mMmFOo77Dj90Cn(N*pJtODWKjIxPuJfjUi~r89h#ZDFfpTNzn~ zVHlo)FFXj$;B*FNcmN)XVOK}h#g>QgUcLt8XC(x}V{Ktd|VF;CwOkgCwgb}AD zn=b^E1Gh~H&o`Wb{dClC+TL@n1~a=YtM|h49KMG_#qT;pf%S&#b!z>lH5~p>Yq|Zt zJE)y-HZ99{+`%Kyb3Mv1U*QOP$Mz3o;g>6$35qx|QW{1|yV=Qao-d|B(T-j}<6Kjg=oJqfY3aGkPhCp6Il6#!;`8qTmVL zROX@T|A-wtc(Q#t$dpj`U0GBCanF-CQ7>s+}#8Vveav~rM!YGDgUf*Epmn@MJYkPHp zOW`FdR3JM|&nh{+i!Fg`|2+o+3o&#w>DK9~J!lK;B>N!hzStm@C5=={KYFv-`m2?L z7z$Std)8&E_^W}pR6Dq-p8P$OBMo9QR?QE$sVT@q}POg*LdJxi-N?}NWcd-#V zs6`HbNA6ehB=aZ)1{;~i6>Nsa4@jG7HCdb3T*lub*zLf@6{!}btc58HRI!yol8;?U zl3ST1+t|q>$yZ}@NpgosKA1tmCFdgE1lMAX(n;1tiCsyOdzmD+@Lm>)GDT7+lD!!u z{Nzk>8~m)3Bm!BUPqlGIV6(DGf24QnB>DulENg(lT4D2vPlY4B*#Q@ zGJ}L$o=NC;L@U?iXX+(xdHYXj-@JleDxSfpQum%=u4K}=bU_O8v@L9$;i8nzOQ{m6 iEl7bH2`))NfdmUbk=+?elbL><$kdL;=^o+a@bSMl-u!+5 literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/common/ReflectionUtils.class b/target/classes/org/mbassy/common/ReflectionUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..c1b4f82a030ccab6d70fe8bf06fbd94f0505d71c GIT binary patch literal 3388 zcmb7GTUQfT6#fpm41_`Drh=CON+k&xrD`id1W{Tlparkk+mMWLV3NVfM5S8o#a{dg z?PFhjwX18PwyQq2Yxx`cL;6(vota!lNCRapX3p7XpMClEw|C~{Kfk^JFo;JH)Z?;( zD-qP;asc5x2`F9ygkzg`>d$Wb>wvE;mp~t?>2uiUXAKr348F~G?1nKM zlj8n0{x`7FH0NlkrD<*1<$;1_@=w7{1-+y#5t=hHIn629MDbqjLf3RuE!MR>sA+(q z5c~;4HznuJ^i7eRiHv1sOufWYl2%XrKi+lt;wnMx`XG><)o4~a?Ah4Az=;i&s-^6? zkfn@oqLd|V1-61wf%_XHQ_HCjk20q=Q>RiGoDG~Urq{9pXZw5x&n+x+jQHoloMRig zOwtc@QK{(Dof<_ETRF>rj=`4oM(NkTU|)7JBx(hB%I zCs03}GKa|K92D4ADsJI68%xC< z+-4A!_{sx`v!f#Ql2);T+bTZ8T@_vER&fuXtGJH`EVqgi7*X*B?sCXiF1lq?@g=@e z@U@DE_(q^x{#w)=>I%M9@g2Tbu>(5=cCWusndkyr{F&xC61z6)#@BN5D#ALlqfhH8 zQ{!)i#~7ApPhZqupi)JzB3FNF!cxeR$)m)E1-u z*sC;w7nVQJbujF#RQ&ZbCS&AoSxY4Aale~l7x}TR)oVM}eqc;!DaW!2yqv?{H-|v4 z-$N3HmUB~q_7LMbNN^4l z*g*n&WDT$<%fOCq64_V_!yrP+)f)5nTDwyK}g3OV8yucGLuO8P35NSo*GqnGL?Xs6SB>D7uxs|(%+|}XI Ezx#1Rb^rhX literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/filter/Filter.class b/target/classes/org/mbassy/filter/Filter.class new file mode 100644 index 0000000000000000000000000000000000000000..3a1be1faa74e8df1b915d6f9e4dda31f9889696c GIT binary patch literal 500 zcmah`(MkeA6g^|wrdC?^P(&0xND;KqOAkgMVIb%#x&_fgjB;B>#@X1N753M9=mYww z=&F!{LJz~-bI-Zw&b{;f^Z5nf1_uuI9PB%&2;?SGCnQk0xNME&M7mm zz6y-ANyLiVRmI&^D~tknJYaR|zAJ4ha@!QgGN8v9s@=0op&usEaGGJ8;+(jiHWN7N zCB~{TJ*!v^G}Vm>t+XmMvB25VgXACK_Q(>~god4~|46!_R>PEqPVdR{o9%|c$@&ss zMgdt?p0DQ{n#SDLPd|u7rMl;J{JP)lcyInomu(MM7cn1HpuE^?XE36n6*yg_mL^+F uUlkC@@Y&!G6Ip>Qa*Q?^W#I7HnnigO_&#P_WHw9K#twF;;VT58gz_&M%#PXs literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/filter/MessageFilter$All.class b/target/classes/org/mbassy/filter/MessageFilter$All.class new file mode 100644 index 0000000000000000000000000000000000000000..bf1b2d83e5fcc44d5976f2b6369f27f231ca8ada GIT binary patch literal 554 zcma)&%}T>i5QWdAX;W*pwf@xNLP5pWg+aF!Q3`?(b)j_O?j~N_l-op-+k)@p#+Bg0 z2k@c9xoJVP3negJscud@l(V zFO1~XXz0u2R``mYnimH`bwv^yJvD9J4^u|Zlp>TbqDUqkCDK%~x$DJA&mHf}GEGHKo@Z)@sW!n8K_DMB6MIc>AjZN~BI>!<{y+x0J?pgY2zfb{kv2=s0ED6n zQ!RNFLg^+RC4re|{@};-14EXPM}e^Yk4*{H-?Ipte}FiI#rNj3^6@RsJdUHo%H>R) z^ZT!$m#kdmXv#nxa~#W)29&VCim-?ZM+2X7&T+QvXA|*FZe--vq&g){M-@xi*DTcd T&EjV{BUg9|xR2PeF^|R<5WjbW literal 0 HcmV?d00001 diff --git a/target/classes/org/mbassy/filter/MessageFilter.class b/target/classes/org/mbassy/filter/MessageFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..42cd6e31b87251414d854ed52dc89d8c73afd019 GIT binary patch literal 263 zcmX^0Z`VEs1_mPrUUmj9b_Q;C1|CKRX2+ZyMg|_wyu8#R=bXgi;?!bB2KL0{%QjC^^+FGbfdiLC80?xHvH##4AZH z(gPX6$RM9zl&+tf1hk`4KMgFQkD`o`K>*z@Mh0a9npA-9XJcewWMBe%je&uYg^_`c ZfgQ+VV&DLioD8f$nhU6c8HiaJBmvVCMT!6b literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/ConcurrentExecutor$1.class b/target/test-classes/org/mbassy/ConcurrentExecutor$1.class new file mode 100644 index 0000000000000000000000000000000000000000..4c36aaeaaf5e35bd9e51e704888783cd7337d566 GIT binary patch literal 1108 zcmah|Sx*yD6#foOJMAz)S){mNMG%If3?QyFMAKj*DUtx;#i!|TEkmYv%q#@{l_oCe zgFnC@WjuFU+nTzh>Am-C-}%lm-+z4l24EePJao(%$YI{V9V`rC5te}^lnpj4>$t1q zo{kkl?#%J$6Y0f-$=a!N=GeX?8}@!8rBnCCCLvq#*t|t3T7Pm_IwEM*;8+kwYn~LZ zl4f0mhbovbUJG2uKXO7($!(<;pLh`g1oNeoBCPsO6baVM)`GBMH|s23*wsL~Nf-(l zZ=VY{iGy%{joE07=QFUi)I+8g$OhXoQS5|V2<;Gz?Q>VWjlDodI&3cPw}8F+sc>Ts z$sc$P>BLFMWUD=hZHJa7i9O$TJACXaQ&9C(dIC}kTa0KN1WD+M7oMuW*k8qpYBKj~ zTe^M_d9tx9;*;Q5$Et}nteY?}WWvOK9UCSd;GvF3CLSyKiHWD!G;sw}gwlU{MJRN~ z*(!%HldAq!4`HGk(&-;Z{WBF|I80=>P{OqJx~DUpRZ^QWXsNS@*}+8=izZ>LJ?ulT zDRw>IR~y34JjwAcr(aNP;*^Auu1WQupz}5-V($%CV(s*82v&LdbG&zXP@$k2%^%Ak z|4j_Dc7$IW7~mgZl$Sq(%AYato}~ed@|k5HU=9U7&GcOOf&jb)@poTD?H~+*V~zF t)+k1q@+V}w!2L8cZNLr;gWX7p%;08vmvD=34YRn734R$2tn&@zegfl5`8fap literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/ConcurrentExecutor.class b/target/test-classes/org/mbassy/ConcurrentExecutor.class new file mode 100644 index 0000000000000000000000000000000000000000..9b28e0cd16cdd86e09997fbd8d8aced623da087c GIT binary patch literal 2974 zcmaJ@S#uOs6#ja)J4_maAt59o5EdcZFoJ-Z#9#;@!6YDA*hKBjG)couckJmV3GRvu zuDFY$7Cb=78$1LeCFQd}z+d8nMXC7R?wQO?9GF!4E`82-&-u>M_xFE)xdUJeeu!Zm zh6P4qSc}mZ4&kuCkr={q^JokL$13m~j#uD$ydY;UhN&li;YH9eCbyCTsTh_*mqI5h zFpdfNFa%C6!YQN$GBMO*I0{paRt0J>DQ7Q5VaqWag9BIKbPP?<1*Qb16+nU7XPUa* zo7S>fJ*yznWtfK3tsvOkGO8fdYo&ArmHmdP54xE#-5%D)(%h-)w~|_VRI?4a&TWRA z34<7``z?Dskr^Z8OrqB^ldf&+rn7rmPr8m}cPdzHyJq2qf)&mEC$-aBBCVO@iG!|b zO3=x9%1V>Ol&wup>bCUMGuw5HbRs$LXrfn3r#%m7wr1PwIiz)yL%`reA$?6ncJ!H! zZrkppqo;OHC-q5(Hj|^8B9Y?#h^=ScG^uDTfA}8Pac!LdIjTJ4`gnxqb05`naZs?K zJVHKDL%Mz1NJ=Qx)(L8kYH3#{$l^)yHW~7}=p$FE=`ppvlbsDGQ4ZKMURlJ3_ zRh-2}fp=8Ai}zH#k8=uE6$~hsQ7S&bMin38BRZ(!V|=3GQ=C`OP%x_~K0Z?dpQ-p9 zU(ovk`jBHA=J-z6NT*~CG|m@VlXap=7MdKVEc;ZGV>PMx5?`tK8t3Kg8}^kgS*hxJ}F4vcJWkEVCSW*!4W8lZGq)_sFF3g}j z7^`5JsZaH4$q79*JYnlv>VRdX6*M=u6y;(Oqj}9jRn?a5da}brvhG+$w!7|}$U-yb zJIs9!n{NU^EtO&~DL%<(tFz^(f@pTabyC)pNxBQ96d65iWb_fQ@rr@I!I9zJL-%cZ zc~REyQf*2zoMGM07^WuMnSyoA`^wWYuO{uOoMdV_UNauHwIr)9YUDZ>TTBVFMIB8{ zXxTx1nso7_MU;@~EvobKUS{RIF3FprnXIE{sAjoT&NW=aN*wU5U{EvkCL)`?NfzV9 zEc#W@nd9F(qNFgW7#!CLTcVZc-shsMR@AY4Aa?K)4{|5w>!s9Aew94#(&V}%+Ic%i zv4+==b>6)q$nQRz7N5;rCphvdfN<+gDAzd&poPzfx49IpoU6V7+R)C`BKEaS+>y%X z5JZS$L&L8K3X+*`RXu-kaS7eH!uikW z@mM}XlsH*_f--^_^&ZO+GPsq}+sJ$sQM>pb;w>_cCpilfIYgWte0zzjZal^3G}EAm zHiVGEZp!s~QM=|v2;9AbUEJsO0Y737o~DO;=}8l%{PX*GzK^kw(!SI)x!CSP^d66o zFuDT$0s}J4Ow+&6J|Zv}`dhx*e~&(di$wMd9DpM5>|I3e5-EU#JTZVFJj^jhTLecS F`X8Jb28;jz literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/MBassadorTest$1.class b/target/test-classes/org/mbassy/MBassadorTest$1.class new file mode 100644 index 0000000000000000000000000000000000000000..8edf17c213f3f13b22b6bcd9c5cde58d8a27248b GIT binary patch literal 1767 zcmb7FZBrXn6n<_JvLxMF8bS*dY&s%MLTNxNR#LG|qzZB<3rp|(S1ilC5_u)Uc%C zQw=qN;T^*s??}(L{6IRvn*3UN(lMnkFj;$I>=>mmuus0XO~dzhOUo51qvd)GNd}KCe|%ctQqPEW86`^IvK%Xz z7ud?59`-+=^-NMOGz1b=wu%_o@~#!tbu$ z?qNd5CA_ZV0xs$p#)ytlOlVlqv5L9moWOQFBfL;uoo-;MttSiF*D4)Ig4((=6}v5aI&^is%3} zxgKeJ3`+Tqmfr#mPO0;kkfCpoTK$sr<{|zD!&b%L literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/MBassadorTest$2.class b/target/test-classes/org/mbassy/MBassadorTest$2.class new file mode 100644 index 0000000000000000000000000000000000000000..2dc027d32c32a17a9510922445b8f8723bcbae46 GIT binary patch literal 1979 zcmb7FZBrXn6n-w*WJ%nXl$P=$Z9`3KNJ@k89V#}YRKWzWL9q38ldQ0?+0E<=n(uz` zZ}`Q}I+8jv(|-3SIpfQ7H<5;sI5smm=kB@Zoco;ToV)wmpT9lyBLyT$da9lGAE$*gLN28%5XE z+hx=97X>cJa4)F!XHccB7BJW4vg7XNn>F3@+WE>dmELe%hQ<6n(@W0_Or7bmMLg2$ zj%l0zlE8PF!EVn1_jz2;1U^f`l#&AUEq~oOD^3>aM zsL0xiUAG+1w0A3pzvnbm>}dEHIStp4(lCV=HB3u!1y?o9AgAJ?hDZ2B;QCn|k*U*t zRoxiq)L)c}Pc?jo&sBV(;Y;WmYEsm}n#RNFw{&|qU-exAT@FmEVYmV}PYPN{Z3Z>V z^!B)&D$LKP!UlOM-*FCDg@Lc6*dDP2PJQXCZO=EFyuZEsy=69yifOT^aO&9N+3?{1 z^BQ1;-XL{jneGJ($?M`@oP8v4=`6Io5UnnWMXznw2SjuF)t(zqaRL%D);+{@@|$dr z&n6jqlVcjwTdi;^=lWrEd$>5;hwM~a891F;`>*X`-|0rMkj%dwnc!l`)M-Exw}fYr z_EWt4185s5zS$@EVwi-IOv=#*s5$+i<^>2f?*+dLL;TO+I=x@w>K>n?)Kl5q5k&UZ z5r(pJM~Gx6l_MzG$B6zI#-u4@i~&dR2Bj9Rr*IRqd=kha*UczY`o(B-enISRWa&z> z@jZ&iP#-?V@PcxTI2Vb9X!d6eO)4F{u)|ekGTK`y(x%a*NYlA*BjSg@o-)(&PdI;! z5ymD*FLW?EbIs`B;^-wVI~eQWvV<9dgjpas@P;U`xN2eyHVJT$L5q7X7U8jM3u`#U zI=<%aH(^Ojggu5lZs9g#ud=i}ULiR#dKd62v$LpP!3y)ghSyp0I>~&4s|eoYegZ>( oAf|}lQKIE7T88j87N`}TdxscEHFw6T-B?%cHHbKJW=K7JP)HY9a(bZ?}_nNO#ukgM?q=C;g&- zz{E%52Y-M+%6N8#vVdz$)4g+N&OP^@xi|Ck*Y_U)Zeq270W20_pi;mnszuarwSXy9 z4J;Y31O~#63d+|7CL5mLw%n#21bf!=H5$9+`CBpwSBP3wT7?eY60#+y2B@#la|IT%Rg`GeFF~!Dp~V+Vgxu* zF`3o1b=!K`+>s7DIGY*XyG?<)Oi=nZ3{cs{6!e60fxg9fd<@6qKuABH;H0D|R9!Y- zJCW~muq-?DT;*8x#B31SPFh|0Z!o7_X~&l?+3LFWOj0UyZwQR13OsVt0%uc42eo5{ zPldr(LzGH!7r5#JJBCyIn)H^D;}0lSYhNI$-;n!6FNf3o4iE#p#TkChWL?A%M(8ma z&Jh(e)ak_xh1zHIeMSEDAwe-Yc*n%=6OuTDzWXZh$}c{K4G DpdD=L literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/MBassadorTest$SubTestEvent.class b/target/test-classes/org/mbassy/MBassadorTest$SubTestEvent.class new file mode 100644 index 0000000000000000000000000000000000000000..d813302a5ed30c038f36bb5e19878cdd36f71cc9 GIT binary patch literal 426 zcma)2%SyyR5UkGA#OP{N@Tw?24hFm`9$XZJ=w+9^Pj&|#$y{@yUeX4b4mv2D#Gmp_v!b zjzv+jlICVA%89V5<-ej=KdFk4jGAIBtNLakbX{-ED?L5N4Du~T`gx(EN1o;{0&NZ`Q_u}wJq*GR(t(z@QhQ7_+J)_^@Y{BP|c zzPt!yz%1)H;Ge#O-dJ!DG6pOIuUKJBy4**^S?jNLxQS5dOAJ8&jjLwQALq;=xuh;8iJTQ7A+`SPC9D=~B0(yRbPtxf6S-c zOA{G+p;lpPj8KWk6FrfkcbcmnQAffYGn7xHl1Yc5xZfHuSY1673{_t$ag|O2Vfs9X z$W!-q$fE%_GQ)GHmE6gg!Rd=QIiFG$2Dhh_Fx`m9v7oO#Uz?FP2?*YMmuIB-P@9au z_Z>uCRBm)?LUAGUQa^Zy*)%R3R2W+Sp3H@f1shuoyMH&pP|FiXT#dZzU@Sr!r1@JM zc4+Tqx(B02Rpu5oNO+M@2Su`gT6+f8ensJShT=1Qh4Trk5rB^X literal 0 HcmV?d00001 diff --git a/target/test-classes/org/mbassy/MBassadorTest.class b/target/test-classes/org/mbassy/MBassadorTest.class new file mode 100644 index 0000000000000000000000000000000000000000..ca58d0820b7aac2af908edd86d981a27d599a151 GIT binary patch literal 4100 zcmb_f-*X$)89giSYUTCDHnAK~u!9qvSdJT1l7_Z+;>Ix!I5-Z(szc0=vRW_JCh|(G zR>r14+JK=+LrY2v^mp2{H4n@HosQ$+nNFYk#CvD@+`oZoanIdd{cs`$9-^^z@80{} z?|$Do=eu|H&o4gxJAnQ8K*In`6|)*5qO~;0qJ3UN6mu=e!WJ`URh-kHiJ6=R9i|wZ zSLlKt^)n3)^39wtXm|mxh9W#+Q4+m{&Z(L=Uld&3f%lKs;XTT&Y+BNeR7mU5jjbE6~wARxD*gNEu8mHcN#vK8J7G{eEfqX#wzAI1&*Rtww37nZ9?+ahCWXw)62}rUNyu10xYaYBXP1I_aEtZO{(&c!D1WD;T;&T%K!tIlH#BCi}@$gacCSje*mt502X8Yy6 zscat*#i8Rt>?fD?YFk1_wSFpqOX#m>Ea1AOqJ{S#QOi0m;*yRZJ-n~um$99*GU3d{AWmTNS_4@q1joo#lRE`3D_;#5EG8<4^drj=x|5m%*k;(^=Ot=v^u1EUUl^B6(bFshZ*px7>zR&vRwoHd7;hBaW;&OPn+H ziK=JJwB{@#!po0WOS^zII4DPWcec4PO-k~( zA9q{9wH?ntyVI^=a^)?nfBRRh$%EArc6n+Oo=*>h_0e@AffWRQ(jI)1LT+2 zuq}?e$qw?AQg8UC_3$EX8(CegtN_Y>R$Xdz)81iOps z+C!jIg!&+1944%f5VRA7a*7~k2wawMp2IQBbH>GSyhh85L_lcuX=Xi7cpt|T^t%Vo zgrp5~ouDAfHHUl(RBYqVlroW*DnwogiEP37kjM_aSRpbXEf^`2_AulPI@?Uz2Gta+ zp;5)DfI*PeDYCo`a&?-KH{vu+gd%>meZlAjr=Qkv+8!TX$Gt3@E>S;0M=5tmU-y~4 ziRi#}>}Zd#;l6eB@L5hM9NFoQ#IY+RPZiv57nRpL$o>uNmiV%;Q}p&o_=ljEwU~%i zd0pA~mnh@<=DINN6XyM_TcH!03k^I*X3{rlKor;%HIPOtzDxa2^C#hH^y3WK%W}ob zTyvhYuW;s7JjD`Q#09)b<}b1ER`3Q^`E-LS5E^)iMK~R5K&54y&wPy9Nz(f?RIU(X zr@8Bu${2C2XyC6E4SW=8pcNnDNh+s_(OqL)Gt_Vo-=m&lPhq52sE`@*Bg(p`V)b;z z>Y0kwv%Utw1BfC>Lk&ehReUe_3k33CL{&WZIr=J{O|>3@!JQ(^X&>9Y^d9B+*NCsq T_5tcdW1C{z!+G)P4x0Z1f5HXT literal 0 HcmV?d00001