de.skuzzle.jeve.Event Maven / Gradle / Ivy
package de.skuzzle.jeve;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
*
* This class is the base of all events that can be fired. It holds the source
* of the event and provides methods to stop delegation to further listeners if
* this event has been handled by one listener. Events are meant to be short
* living objects which are only used once - one Event instance for each call to
* {@link EventProvider#dispatch(Event, java.util.function.BiConsumer) dispatch}
* . Any different usage might result in undefined behavior, especially when
* using the {@link #isHandled()} property. Also, to prevent memory leaks, Event
* objects should never be stored over longer time.
*
*
* Events explicitly belong to one kind of {@link Listener} implementation which
* is able to handle it. The class of this listener is passed to the constructor
* and queried by the {@link EventProvider} when collecting a list of targeted
* listeners for a dispatch action.
*
*
*
* Events are used in conjunction with the {@link EventProvider} and its
* {@link EventProvider#dispatch(Event, java.util.function.BiConsumer, ExceptionCallback)
* dispatch} method. The dispatch method serves for notifying all registered
* listeners with a certain event. The EventProvider will stop notifying further
* listeners as soon as one listener sets this class' {@link #isHandled()} to
* true
.
*
*
* Note on multi-threading
*
* Event objects are not thread safe! Some EventProviders dispatch events
* asynchronously. If the same event instance is used within different threads,
* avoid modifying properties of the Event. Those modifications will result in
* undefined behavior.
*
*
* @param Type of the source of this event.
* @param Type of the listener which can handle this event.
* @author Simon Taddiken
* @since 1.0.0
* @version 2.0.0
*/
public class Event {
/** The source of the event */
private final T source;
/** Whether this event has been marked as handled */
private boolean handled;
/** The class of the listener which can handle this event */
private final Class listenerClass;
/** The store from which this listener is currently being notified. */
private ListenerStore store;
/**
* Whether this event was prevented the last time it was passed to any
* dispatch method.
*
* @since 3.0.0
*/
private boolean prevented;
/**
* Map for assigning further objects to this event. Map is lazily
* initialized.
*/
private Map properties;
/**
* Creates a new event with a given source.
*
* @param source The source of this event.
* @param listenerClass The type of the listener which can handle this
* event. This value must not be null
.
*/
public Event(T source, Class listenerClass) {
if (source == null) {
throw new IllegalArgumentException("source is null");
} else if (listenerClass == null) {
throw new IllegalArgumentException("listenerClass is null");
}
this.source = source;
this.listenerClass = listenerClass;
this.handled = false;
}
/**
* Returns the Map that is used to store properties in this event instance.
* All modifications to that map are directly reflected to its Event
* instance.
*
* @return The property Map of this event.
* @since 3.0.0
*/
public Map getProperties() {
if (this.properties == null) {
this.properties = new HashMap<>();
}
return this.properties;
}
/**
* Returns a value that has been stored using
* {@link #setValue(String, Object)}. As this method is generic, the value
* will automatically be casted to the target type of the expression in
* which this method is called. So this method has to be used with caution
* in respect to type safety.
*
* @param Type of the resulting value.
* @param key The key of the value to retrieve.
* @return The value or an empty optional if the value does not exist.
* @since 3.0.0
* @see #getValueAs(String, Class)
*/
@SuppressWarnings("unchecked")
public Optional getValue(String key) {
if (this.properties == null) {
return Optional.empty();
}
return Optional.ofNullable((E) getProperties().get(key));
}
/**
* Returns a value that has been stored using
* {@link #setValue(String, Object)}. The value will be casted to the given
* type.
*
* @param Type of the resulting value.
* @param key The key of the value to retrieve.
* @param type The type that the value will be casted to.
* @return The value or an empty optional if the value does not exist.
* @see #getValue(String)
*/
public Optional getValueAs(String key, Class type) {
if (this.properties == null) {
return Optional.empty();
}
return Optional.ofNullable(type.cast(getProperties().get(key)));
}
/**
* Adds the given key-value mapping to this Event. The value can be queried
* using {@link #getValue(String)}.
*
* Note: Storing properties on events is generally discouraged in
* favor of creating explicit attributes with getters. However, there might
* arise situations in which you must attach further properties to an event
* without being able to modify its original source code. In these
* situations, storing properties with a String key might be useful.
*
* @param key The key under which the value is stored.
* @param value The value to store.
* @since 3.0.0
* @see #getValue(String)
* @see #getProperties()
*/
public void setValue(String key, Object value) {
getProperties().put(key, value);
}
/**
* Gets the source of this event.
*
* @return The source of this event.
*/
public T getSource() {
return this.source;
}
/**
* Whether this event was prevented the last time it has been passed to any
* dispatch method of an {@link EventProvider}.
*
* @return Whether this event has been prevented.
*/
public boolean isPrevented() {
return this.prevented;
}
/**
* Called only by {@link EventProvider EventProvider's} dispatch method if
* this event was prevented from being dispatched.
*
* @param prevented Whether the event was prevented.
*/
public void setPrevented(boolean prevented) {
this.prevented = prevented;
}
/**
* Gets the type of the listener which can handle this event.
*
* @return The listener's type.
* @version 2.0.0
*/
public Class getListenerClass() {
return this.listenerClass;
}
/**
* Removes the provided listener from the {@link ListenerStore} from which
* it was supplied to the EventProvider which is currently dispatching this
* event. Hence this method can only be called from within a listening
* method while the event is being dispatched. Calling this method on an
* Event instance which is not currently dispatched will raise an exception.
*
*
*
* public class OneTimeUserListener extends UserListener {
* @Override
* public void userAdded(UserEvent e) {
* // logic goes here
* // ...
*
* // this listener should not be notified any more about this kind of
* // event.
* e.stopNotifying(this);
* }
* }
*
*
*
* Removing the listener will have no effect on the current dispatch action.
* Even if you remove a different listener than {@code this}, it will be
* notified anyway during this run, because the EventProvider collects the
* Listeners before starting to dispatch the event.
*
* @param listener The listener to remove from the currently dispatching
* {@link EventProvider}
* @since 2.0.0
*/
public void stopNotifying(L listener) {
this.getListenerStore().remove(this.getListenerClass(), listener);
}
/**
* Gets the {@link ListenerStore} from which the currently notified listener
* has been retrieved. If this Event is not currently dispatched, an
* exception will be thrown.
*
* @return The ListenerStore
*/
protected ListenerStore getListenerStore() {
if (this.store == null) {
throw new IllegalStateException("Event is not currently dispatched");
}
return this.store;
}
/**
* Sets the ListenerStore from which the currently dispatching EventProvider
* retrieves its Listeners. The method will only set the store once. A
* second call to this method on the same event instance has no effect. This
* is to allow wrapping EventProviders so that the store set by the
* outermost provider is not overridden by an inner (wrapped) provider.
*
* @param store The listener store.
* @since 2.0.0
*/
public void setListenerStore(ListenerStore store) {
if (this.store == null) {
this.store = store;
}
}
/**
* Gets whether this event was already handled. If this returns
* true
, no further listeners will be notified about this
* event.
*
* @return Whether this event was handled.
*/
public boolean isHandled() {
return this.handled;
}
/**
* Sets whether this event was already handled. If an event has been marked
* as "handled", no further listeners will be notified about it.
*
*
* Note that setting an event to be handled might have unintended side
* effects when using an {@link EventProvider} which is not
* {@link EventProvider#isSequential() sequential}.
*
*
* @param isHandled Whether this event was handled.
*/
public void setHandled(boolean isHandled) {
this.handled = isHandled;
}
}