All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.teamapps.event.Event Maven / Gradle / Ivy

There is a newer version: 0.9.194
Show newest version
/*-
 * ========================LICENSE_START=================================
 * TeamApps
 * ---
 * Copyright (C) 2014 - 2023 TeamApps.org
 * ---
 * 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.
 * =========================LICENSE_END==================================
 */
package org.teamapps.event;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teamapps.ux.session.CurrentSessionContext;
import org.teamapps.ux.session.SessionContext;

import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Represents an event that can get fired.
 * 

* Listeners can be added to this event using the various {@link #addListener(Consumer) addListener(...)} methods. *

*

SessionContext-bound Event Listeners

* Note that if a listener L is added while the thread is bound to a {@link SessionContext} A (so while {@link SessionContext#currentOrNull()} is not null), * L's execution will be bound to A by default, i.e.: *
    *
  • When this event fires, the SessionContext-bound listener will get invoked bound to the SessionContext A, * regardless of the SessionContext (or the lack of it) the event was fired in.
  • *
  • When the SessionContext A is destroyed (and thereby fires its {@link SessionContext#onDestroyed} event), the listener is automatically detached from this event.
  • *
* You can prevent a listener from being bound to the current SessionContext, * by using one of the {@link #addListener(Consumer, boolean) addListener(..., boolean bindToSessionContext)} methods. * * @param The type of data this event fires. */ public class Event { private static final Logger LOGGER = LoggerFactory.getLogger(Event.class); private final String source; // for debugging private final List> listeners = new CopyOnWriteArrayList<>(); private EVENT_DATA lastEventData; public Event() { StackTraceElement stackTraceElement = new Exception().getStackTrace()[1]; this.source = stackTraceElement.getFileName() + stackTraceElement.getLineNumber(); } public Disposable addListener(Consumer listener) { return addListener(listener, true); } public Disposable addListener(Consumer listener, boolean bindToSessionContext) { SessionContext currentSessionContext; if (bindToSessionContext && (currentSessionContext = CurrentSessionContext.getOrNull()) != null) { listeners.add(new SessionContextAwareEventListener<>(currentSessionContext, listener)); removeWhenSessionDestroyed(listener, currentSessionContext); } else { // just add the listener. It will get called with whatever context is active at firing time listeners.add(listener); } return () -> removeListener(listener); } public Disposable addListener(SelfDisposingEventListener listener) { return addListener(listener, true); } public Disposable addListener(SelfDisposingEventListener listener, boolean bindToSessionContext) { AtomicReference disposable = new AtomicReference<>(); disposable.set(addListener(e -> { listener.handle(e, disposable.get()); }, bindToSessionContext)); return disposable.get(); } public Disposable addListener(Runnable listener) { return addListener(listener, true); } public Disposable addListener(Runnable listener, boolean bindToSessionContext) { return addListener(new RunnableWrapper<>(listener), bindToSessionContext); } List> getListeners() { return listeners; } /** * @deprecated Use the {@link Disposable} returned by {@link #addListener(Runnable)} instead! */ @Deprecated public void removeListener(Runnable listener) { removeListener(new RunnableWrapper<>(listener)); } /** * @deprecated Use the {@link Disposable} returned by {@link #addListener(Consumer)} instead! */ @Deprecated public void removeListener(Consumer listener) { listeners.remove(listener); // in case it is not bound to a session listeners.remove(new SessionContextAwareEventListener<>(listener)); } /** * When the session gets destroyed, remove this listener (preventing memory-leaks and degrading performance due to stale listeners). */ private void removeWhenSessionDestroyed(Consumer listener, SessionContext currentSessionContext) { if (this != currentSessionContext.onDestroyed) { // prevent infinite recursion! // use a weak reference here, so the fact that this is registered to the sessionContext's destroyed event // does not mean it has to survive (not being garbage collected) as long as the session context. WeakReference> listenerWeakReference = new WeakReference<>(listener); currentSessionContext.onDestroyed.listeners.add(aVoid -> { Consumer l = listenerWeakReference.get(); if (l != null) { removeListener(l); } }); } } public void fire(EVENT_DATA eventData) { this.lastEventData = eventData; for (Consumer listener : listeners) { invokeListener(eventData, listener); } } public void fireIgnoringExceptions(EVENT_DATA eventData) { this.lastEventData = eventData; for (Consumer listener : listeners) { try { invokeListener(eventData, listener); } catch (Exception e) { LOGGER.error("Error while calling event handler. Ignoring exception.", e); } } } /** * May get overridden. */ protected void invokeListener(EVENT_DATA eventData, Consumer listener) { listener.accept(eventData); } public void fire() { fire(null); } public void fireIfChanged(EVENT_DATA eventData) { if (!Objects.equals(lastEventData, eventData)) { fire(eventData); } } public Event converted(Function converter) { Event newEvent = new Event<>(); addListener(data -> newEvent.fire(converter.apply(data))); return newEvent; } private static class SessionContextAwareEventListener implements Consumer { private final SessionContext sessionContext; private final Consumer delegate; public SessionContextAwareEventListener(SessionContext sessionContext, Consumer delegate) { this.sessionContext = sessionContext; this.delegate = delegate; } public SessionContextAwareEventListener(Consumer delegate) { this(null, delegate); } @Override public void accept(EVENT_DATA eventData) { if (sessionContext != null) { sessionContext.runWithContext(() -> delegate.accept(eventData)); } else { delegate.accept(eventData); } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SessionContextAwareEventListener that = (SessionContextAwareEventListener) o; return delegate != null ? delegate.equals(that.delegate) : that.delegate == null; } @Override public int hashCode() { return delegate != null ? delegate.hashCode() : 0; } } private static class RunnableWrapper implements Consumer { private final Runnable runnable; public RunnableWrapper(Runnable runnable) { this.runnable = runnable; } @Override public void accept(T eventData) { runnable.run(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } RunnableWrapper that = (RunnableWrapper) o; return runnable.equals(that.runnable); } @Override public int hashCode() { return Objects.hash(runnable); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy