com.google.code.eventsonfire.Events Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2012 events-on-fire Team
*
* This file is part of Events-On-Fire (http://code.google.com/p/events-on-fire), licensed under the terms of the MIT
* License (MIT).
*
* 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 com.google.code.eventsonfire;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import com.google.code.eventsonfire.Action.Type;
import com.google.code.eventsonfire.error.DefaultErrorHandler;
import com.google.code.eventsonfire.error.ErrorHandler;
import com.google.code.eventsonfire.swing.SwingEventHandler;
import com.google.code.eventsonfire.swing.SwingEvents;
/**
*
* The most important class for events-on-fire.
*
*
* For a detailed usage description see Usage on Google Project Hosting.
*
*
* All references to producers and consumers in this class are weak, unless otherwise noted. Any producer or consumer
* will get garbage collected, if there is no reference to it outside this class. All checks will be made on identity
* instead of equality.
*
*
* @see Usage on Google Project Hosting
* @author Manfred Hantschel
*/
public class Events implements Runnable
{
/**
* The runnable for the thread for firing the events
*/
private static final Events INSTANCE = new Events();
/**
* The thread for firing the events
*/
private static final Thread EVENTS_THREAD;
/**
* The thread local variable containing the count for disabled events
*/
private static final ThreadLocal DISABLED = new ThreadLocal();
/**
* The default maximum number of threads for the executor service
*/
private static final int DEFAULT_MAXIMUM_NUMBER_OF_THREADS = 4;
static
{
registerStrategy(new EventHandlerAnnotationStrategy());
registerStrategy(new PooledEventHandlerAnnotationStrategy());
// initialize swing events (static init)
new SwingEvents();
EVENTS_THREAD = new Thread(INSTANCE, "Events Thread");
EVENTS_THREAD.setDaemon(true);
EVENTS_THREAD.start();
}
/**
*
* Binds the specified consumer / listener to the specified instance of class of a producer. The producer may either
* be an instance or a class reference.
*
*
* - In case of an instance, the consumer / listener is notified, if the specified producer fires the event
* (producers are compared by identity rather than equality).
* - In case of a class reference, the consumer / listener is notified, if a producer, that fires the event, is an
* instance of the specified class (e.g. if the specified producer is a
Object.class
reference, then
* the consumer get notified by all events it can handle).
*
*
* The consumer must contain at least one @{@link EventHandler} public void handleEvent(* event)
* method, otherwise an exception is thrown. Both, the producer and the consumer must have references outside of the
* Events class. All references within the Events class are weak, so the objects and the binding gets garbage
* collected if not referenced. Does nothing if the objects are already bonded.
*
* @param producer the instance or class of a producer, mandatory
* @param consumers one or more consumers / listeners, mandatory
* @return the producer
* @throws IllegalArgumentException if the producer or the consumers are null or one of the consumer cannot handle
* events
*/
public static PRODUCER_TYPE bind(PRODUCER_TYPE producer, Object... consumers)
throws IllegalArgumentException
{
if (producer == null)
{
throw new IllegalArgumentException("Producer is null");
}
if ((consumers == null) || (consumers.length == 0))
{
throw new IllegalArgumentException("Consumers missing");
}
for (Object consumer : consumers)
{
if (consumer == null)
{
throw new IllegalArgumentException("Consumer is null");
}
INSTANCE.enqueue(new Action(Type.BIND, producer, consumer));
}
return producer;
}
/**
*
* Unbinds the specified consumer from the specified instance or class of a producer. The producer may either be an
* instance or a class reference.
*
*
* - In case of an instance, the consumer / listener unbonded from the specified producer (producers are compared
* by identity rather than equality).
* - In case of a class reference, the consumer / listener unbonded from any producer that is an instance of the
* specified class (e.g. if the specified producer is a
Object.class
reference, then the consumer get
* unbonded from all producers).
*
*
* Does nothing if the objects are not bonded.
*
*
* @param producer the instance or class of a producer, mandatory
* @param consumers one or more consumers / listeners, mandatory
* @return the producer
* @throws IllegalArgumentException if the producer or the consumers are null
*/
public static PRODUCER_TYPE unbind(PRODUCER_TYPE producer, Object... consumers)
throws IllegalArgumentException
{
if (producer == null)
{
throw new IllegalArgumentException("Producer is null");
}
if ((consumers == null) || (consumers.length == 0))
{
throw new IllegalArgumentException("Consumers missing");
}
for (Object consumer : consumers)
{
if (consumer == null)
{
throw new IllegalArgumentException("Consumer is null");
}
INSTANCE.enqueue(new Action(Type.UNBIND, producer, consumer));
}
return producer;
}
/**
*
* Fires the specified event from the specified instance of a producer. Notifies all consumers that are either
* directly bonded to the producer or that are bonded to the class, any sub-class or any interface of the producer.
*
*
* Calls the appropriate @{@link EventHandler} public void handleEvent(* event)
method of all
* consumers. Does nothing, if events are disabled for the current thread. Does nothing, if there are no consumers
* bonded to the producer.
*
*
* @param producer the producer, mandatory
* @param event the event, mandatory
* @param tags, optional, can be checked against tags in annotations
* @throws IllegalArgumentException if the producer or the event is null
*/
public static PRODUCER_TYPE fire(PRODUCER_TYPE producer, Object event, String... tags)
throws IllegalArgumentException
{
if (isDisabled())
{
return producer;
}
if (producer == null)
{
throw new IllegalArgumentException("Producer is null");
}
if (event == null)
{
throw new IllegalArgumentException("Event is null");
}
INSTANCE.enqueue(new Action(Type.FIRE, producer, event, tags));
return producer;
}
/**
*
* Disables events from the current thread.
*
*
* Make sure to call the enable method by using a finally block! Multiple calls to disable, increase a counter and
* it is necessary to call enable as often as you have called disable.
*
*/
public static void disable()
{
Integer count = DISABLED.get();
if (count != null)
{
DISABLED.set(Integer.valueOf(count.intValue() + 1));
}
else
{
DISABLED.set(Integer.valueOf(1));
}
}
/**
* Returns true if events from the current thread are disabled.
*
* @return true if disabled
*/
public static boolean isDisabled()
{
Integer count = DISABLED.get();
return ((count != null) && (count.intValue() > 0));
}
/**
*
* Enables events from the current thread.
*
*
* It is wise to place call to this method within a finally block. Multiple calls to disable, increase a counter and
* it is necessary to call enable as often as you have called disable.
*
*
* @throws IllegalStateException if events from the current thread are not disabled
*/
public static void enable() throws IllegalStateException
{
Integer count = DISABLED.get();
if ((count == null) || (count.intValue() <= 0))
{
throw new IllegalStateException("Events not disabled");
}
DISABLED.set(Integer.valueOf(count - 1));
}
/**
* Adds an event handler invocation to the pooled threads
*
* @param runnable the runnable to be invoked
*/
static void invokeLater(Runnable runnable)
{
INSTANCE.executorService.execute(runnable);
}
/**
* Registers a strategy for event handlers. The strategy decides for a method of a consumer, whether it is capable
* of handling events or not. By default there exist strategies for following annotations: {@link EventHandler},
* {@link PooledEventHandler} and {@link SwingEventHandler}.
*
* @param strategy the strategy, never null
*/
public static void registerStrategy(EventHandlerStrategy strategy)
{
if (strategy == null)
{
throw new IllegalArgumentException("Strategy is null");
}
synchronized (INSTANCE.strategies)
{
INSTANCE.strategies.add(strategy);
}
}
/**
* Scans the consumer class using all registered {@link EventHandlerStrategy}s.
*
* @param type the type of the consumer
* @return a collection of {@link EventHandlerInfo}s
*/
static Collection scanConsumer(Class> type)
{
Collection results = new LinkedHashSet();
synchronized (INSTANCE.strategies)
{
for (EventHandlerStrategy strategy : INSTANCE.strategies)
{
strategy.scan(results, type);
}
}
return results;
}
/**
* Returns the executor service for event handler that are executed using pooled threads. The default value is a
* fixed thread pool using a maximum of 4 threads.
*
* @return the executor service
*/
public static ExecutorService getExecutorService()
{
return INSTANCE.executorService;
}
/**
* Sets the executor service for event handlers that are executed using pooled threads.
*
* @param executorService the executor service, mandatory
* @throws IllegalArgumentException if the executor service is null
*/
public static void setExecutorService(ExecutorService executorService)
{
if (executorService == null)
{
throw new IllegalArgumentException("Executor service is null");
}
INSTANCE.executorService = executorService;
}
/**
* Returns the error handler, the {@link DefaultErrorHandler} if not specified.
*
* @return the error handler, never null
*/
public static ErrorHandler getErrorHandler()
{
return INSTANCE.errorHandler;
}
/**
* Sets the error handler.
*
* @param errorHandler the error handler, mandatory
* @throws IllegalArgumentException if the error handler is null
*/
public static void setErrorHandler(ErrorHandler errorHandler) throws IllegalArgumentException
{
if (errorHandler == null)
{
throw new IllegalArgumentException("Error handler is null");
}
INSTANCE.errorHandler = errorHandler;
}
/**
* The queue containing actions which wait to get executed.
*/
private final BlockingQueue actions;
/**
* A map containing all {@link ProducerInfo} objects containing the consumers by the producers.
*/
private final Map, ProducerInfo> producerInfos;
/**
* The reference queue for all weak references used to get rid of them if the object has been garbage collected.
*/
private final ReferenceQueue