package net.md_5.bungee.event;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
public class EventBus
{
private final Map<Class<?>, Map<Byte, Map<Object, Method[]>>> byListenerAndPriority = new HashMap<>();
private final Map<Class<?>, EventHandlerMethod[]> byEventBaked = new ConcurrentHashMap<>();
private final Lock lock = new ReentrantLock();
private final Logger logger;
public EventBus()
{
this( null );
}
public EventBus(Logger logger)
{
this.logger = ( logger == null ) ? Logger.getLogger( Logger.GLOBAL_LOGGER_NAME ) : logger;
}
public void post(Object event)
{
EventHandlerMethod[] handlers = byEventBaked.get( event.getClass() );
if ( handlers != null )
{
for ( EventHandlerMethod method : handlers )
{
try
{
method.invoke( event );
} catch ( IllegalAccessException ex )
{
throw new Error( "Method became inaccessible: " + event, ex );
} catch ( IllegalArgumentException ex )
{
throw new Error( "Method rejected target/argument: " + event, ex );
} catch ( InvocationTargetException ex )
{
logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() );
}
}
}
}
private Map<Class<?>, Map<Byte, Set<Method>>> findHandlers(Object listener)
{
Map<Class<?>, Map<Byte, Set<Method>>> handler = new HashMap<>();
for ( Method m : listener.getClass().getDeclaredMethods() )
{
EventHandler annotation = m.getAnnotation( EventHandler.class );
if ( annotation != null )
{
Class<?>[] params = m.getParameterTypes();
if ( params.length != 1 )
{
logger.log( Level.INFO, "Method {0} in class {1} annotated with {2} does not have single argument", new Object[]
{
m, listener.getClass(), annotation
} );
continue;
}
Map<Byte, Set<Method>> prioritiesMap = handler.get( params[0] );
if ( prioritiesMap == null )
{
prioritiesMap = new HashMap<>();
handler.put( params[0], prioritiesMap );
}
Set<Method> priority = prioritiesMap.get( annotation.priority() );
if ( priority == null )
{
priority = new HashSet<>();
prioritiesMap.put( annotation.priority(), priority );
}
priority.add( m );
}
}
return handler;
}
public void register(Object listener)
{
Map<Class<?>, Map<Byte, Set<Method>>> handler = findHandlers( listener );
lock.lock();
try
{
for ( Map.Entry<Class<?>, Map<Byte, Set<Method>>> e : handler.entrySet() )
{
Map<Byte, Map<Object, Method[]>> prioritiesMap = byListenerAndPriority.get( e.getKey() );
if ( prioritiesMap == null )
{
prioritiesMap = new HashMap<>();
byListenerAndPriority.put( e.getKey(), prioritiesMap );
}
for ( Map.Entry<Byte, Set<Method>> entry : e.getValue().entrySet() )
{
Map<Object, Method[]> currentPriorityMap = prioritiesMap.get( entry.getKey() );
if ( currentPriorityMap == null )
{
currentPriorityMap = new HashMap<>();
prioritiesMap.put( entry.getKey(), currentPriorityMap );
}
Method[] baked = new Method[ entry.getValue().size() ];
currentPriorityMap.put( listener, entry.getValue().toArray( baked ) );
}
bakeHandlers( e.getKey() );
}
} finally
{
lock.unlock();
}
}
public void unregister(Object listener)
{
Map<Class<?>, Map<Byte, Set<Method>>> handler = findHandlers( listener );
lock.lock();
try
{
for ( Map.Entry<Class<?>, Map<Byte, Set<Method>>> e : handler.entrySet() )
{
Map<Byte, Map<Object, Method[]>> prioritiesMap = byListenerAndPriority.get( e.getKey() );
if ( prioritiesMap != null )
{
for ( Byte priority : e.getValue().keySet() )
{
Map<Object, Method[]> currentPriority = prioritiesMap.get( priority );
if ( currentPriority != null )
{
currentPriority.remove( listener );
if ( currentPriority.isEmpty() )
{
prioritiesMap.remove( priority );
}
}
}
if ( prioritiesMap.isEmpty() )
{
byListenerAndPriority.remove( e.getKey() );
}
}
bakeHandlers( e.getKey() );
}
} finally
{
lock.unlock();
}
}
/**
* Shouldn't be called without first locking the writeLock; intended for use
* only inside {@link #register(java.lang.Object) register(Object)} or
* {@link #unregister(java.lang.Object) unregister(Object)}.
*/
private void bakeHandlers(Class<?> eventClass)
{
Map<Byte, Map<Object, Method[]>> handlersByPriority = byListenerAndPriority.get( eventClass );
if ( handlersByPriority != null )
{
List<EventHandlerMethod> handlersList = new ArrayList<>( handlersByPriority.size() * 2 );
// Either I'm really tired, or the only way we can iterate between Byte.MIN_VALUE and Byte.MAX_VALUE inclusively,
// with only a byte on the stack is by using a do {} while() format loop.
byte value = Byte.MIN_VALUE;
do
{
Map<Object, Method[]> handlersByListener = handlersByPriority.get( value );
if ( handlersByListener != null )
{
for ( Map.Entry<Object, Method[]> listenerHandlers : handlersByListener.entrySet() )
{
for ( Method method : listenerHandlers.getValue() )
{
EventHandlerMethod ehm = new EventHandlerMethod( listenerHandlers.getKey(), method );
handlersList.add( ehm );
}
}
}
} while ( value++ < Byte.MAX_VALUE );
byEventBaked.put( eventClass, handlersList.toArray( new EventHandlerMethod[ handlersList.size() ] ) );
} else
{
byEventBaked.remove( eventClass );
}
}
}