From 96eb72e46a755a1bd581269983d1668f3958830b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Sun, 5 May 2024 12:36:02 +0200 Subject: [PATCH] add readOnly support --- .../github/mmm/event/AbstractEventSender.java | 159 ++-- .../github/mmm/event/EventSourceAdapter.java | 720 ++++++++++-------- 2 files changed, 481 insertions(+), 398 deletions(-) diff --git a/core/src/main/java/io/github/mmm/event/AbstractEventSender.java b/core/src/main/java/io/github/mmm/event/AbstractEventSender.java index 5601be5..ef36233 100644 --- a/core/src/main/java/io/github/mmm/event/AbstractEventSender.java +++ b/core/src/main/java/io/github/mmm/event/AbstractEventSender.java @@ -1,73 +1,86 @@ -/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 - * http://www.apache.org/licenses/LICENSE-2.0 */ -package io.github.mmm.event; - -/** - * Implementation of {@link EventSource}. - * - * @param the type of the events to {@link EventListener#onEvent(Object) send}. - * @param the type of the {@link EventListener listeners}. - * @since 1.0.0 - */ -public abstract class AbstractEventSender > - extends AbstractEventSource { - - private EventSourceAdapter eventAdapter; - - /** - * The constructor. - */ - public AbstractEventSender() { - - super(); - this.eventAdapter = EventSourceAdapter.empty(); - } - - @Override - protected void doAddListener(EventListener listener) { - - this.eventAdapter = this.eventAdapter.addListener(listener); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public boolean removeListener(L listener) { - - EventSourceAdapter adapter = this.eventAdapter.removeListener((EventListener) listener); - if (adapter == null) { - return false; - } - this.eventAdapter = adapter; - return true; - } - - /** - * @return {@code true} if at least one {@link EventListener} is {@link #addListener(EventListener) registered}, - * {@code false} otherwise. - */ - protected boolean hasListeners() { - - return this.eventAdapter.hasListeners(); - } - - /** - * @return the {@link EventSourceAdapter}. - */ - protected EventSourceAdapter getEventAdapter() { - - return this.eventAdapter; - } - - /** - * @param event the event to {@link EventListener#onEvent(Object) send} to all {@link #addListener(EventListener) - * registered} {@link EventListener}s. - * @return {@code true} if the event has actually been dispatched, {@code false} otherwise (no listener was - * {@link #addListener(EventListener) registered} for the event). - */ - @Override - protected boolean fireEvent(E event) { - - return this.eventAdapter.fireEvent(event); - } - -} +/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 */ +package io.github.mmm.event; + +/** + * Implementation of {@link EventSource}. + * + * @param the type of the events to {@link EventListener#onEvent(Object) send}. + * @param the type of the {@link EventListener listeners}. + * @since 1.0.0 + */ +public abstract class AbstractEventSender > + extends AbstractEventSource { + + private EventSourceAdapter eventAdapter; + + /** + * The constructor. + */ + public AbstractEventSender() { + + super(); + this.eventAdapter = EventSourceAdapter.empty(); + } + + @Override + protected void doAddListener(EventListener listener) { + + this.eventAdapter = this.eventAdapter.addListener(listener); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean removeListener(L listener) { + + EventSourceAdapter adapter = this.eventAdapter.removeListener((EventListener) listener); + if (adapter == null) { + return false; + } + this.eventAdapter = adapter; + return true; + } + + /** + * @return {@code true} if at least one {@link EventListener} is {@link #addListener(EventListener) registered}, + * {@code false} otherwise. + */ + protected boolean hasListeners() { + + return this.eventAdapter.hasListeners(); + } + + /** + * @return the {@link EventSourceAdapter}. + */ + protected EventSourceAdapter getEventAdapter() { + + return this.eventAdapter; + } + + /** + * @param event the event to {@link EventListener#onEvent(Object) send} to all {@link #addListener(EventListener) + * registered} {@link EventListener}s. + * @return {@code true} if the event has actually been dispatched, {@code false} otherwise (no listener was + * {@link #addListener(EventListener) registered} for the event). + */ + @Override + protected boolean fireEvent(E event) { + + return this.eventAdapter.fireEvent(event); + } + + /** + * Makes this event sender read-only so it discards all {@link #addListener(EventListener) added} + * {@link EventListener}s and prevents future {@link #addListener(EventListener) adding}.
+ * ATTENTION: This is an internal method that shall only be used by code of this library and not by third-party + * users. + * + * @see EventSourceAdapter#readOnly() + */ + protected void makeReadOnly() { + + this.eventAdapter = EventSourceAdapter.readOnly(); + } + +} diff --git a/core/src/main/java/io/github/mmm/event/EventSourceAdapter.java b/core/src/main/java/io/github/mmm/event/EventSourceAdapter.java index fd39674..0f24e65 100644 --- a/core/src/main/java/io/github/mmm/event/EventSourceAdapter.java +++ b/core/src/main/java/io/github/mmm/event/EventSourceAdapter.java @@ -1,325 +1,395 @@ -/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 - * http://www.apache.org/licenses/LICENSE-2.0 */ -package io.github.mmm.event; - -import java.util.Arrays; - -import io.github.mmm.event.impl.WeakEventListener; - -/** - * Adapter for {@link EventSource}. - * - * @param the type of the events to send. - * @param the type of the {@link EventListener listeners}. - * @since 1.0.0 - * @see EventSource - * @see AbstractEventSource - */ -public abstract class EventSourceAdapter> { - - private static final Empty EMPTY = new Empty(); - - EventSourceAdapter() { - - super(); - } - - /** - * @param listener - see {@link EventSource#addListener(EventListener)}. - * @return this adapter itself or a new instance capable to handle more listeners. - */ - public abstract EventSourceAdapter addListener(EventListener listener); - - /** - * @param listener - see {@link EventSource#removeListener(EventListener)}. - * @return {@code null} if the given {@link EventListener} was not registered and nothing changed, otherwise this - * adapter itself or a new instance capable to handle less listeners. - */ - public abstract EventSourceAdapter removeListener(EventListener listener); - - /** - * @param event the event to {@link EventListener#onEvent(Object) send} to all {@link #addListener(EventListener) - * registered} {@link EventListener}s. - * @return {@code true} if the event has actually been dispatched, {@code false} otherwise (no listener was - * {@link #addListener(EventListener) registered} for the event). - */ - public abstract boolean fireEvent(E event); - - boolean fireEvent(E event, EventListener listener) { - - try { - listener.onEvent(event); - return true; - } catch (Exception e) { - Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); - } - return false; - } - - /** - * @return {@code true} if at least one {@link EventListener} is {@link #addListener(EventListener) registered}, - * {@code false} otherwise. - */ - public boolean hasListeners() { - - return (getListenerCount() > 0); - } - - /** - * @return the number of {@link #addListener(EventListener) registered} {@link EventListener}s. - */ - public abstract int getListenerCount(); - - /** - * @param index the index of the requested {@link EventListener} in the range from {@code 0} to - * {@link #getListenerCount()} - 1. - * @return the requested {@link EventListener} or {@code null} if index is out of bounds. - */ - public abstract L getListener(int index); - - /** - * @param index the index of the requested {@link EventListener} in the range from {@code 0} to - * {@link #getListenerCount()} - 1. - * @return the requested {@link EventListener} or {@code null} if index is out of bounds. - */ - public abstract EventListener getRawListener(int index); - - /** - * @param the type of the events to send. - * @param the type of the {@link EventListener listeners}. - * @return the empty {@link EventSourceAdapter}. - */ - @SuppressWarnings("unchecked") - public static > EventSourceAdapter empty() { - - return EMPTY; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static class Empty extends EventSourceAdapter { - - private Empty() { - - super(); - } - - @Override - public EventSourceAdapter addListener(EventListener listener) { - - return new Single<>(listener); - } - - @Override - public EventSourceAdapter removeListener(EventListener listener) { - - return null; - } - - @Override - public boolean fireEvent(Object event) { - - return false; - } - - @Override - public boolean hasListeners() { - - return false; - } - - @Override - public int getListenerCount() { - - return 0; - } - - @Override - public EventListener getListener(int index) { - - return null; - } - - @Override - public EventListener getRawListener(int index) { - - return null; - } - - } - - private static class Single> extends EventSourceAdapter { - - private final EventListener listener; - - private Single(EventListener listener) { - - super(); - this.listener = listener; - } - - @Override - public EventSourceAdapter addListener(EventListener eventListener) { - - return new Multi<>(this.listener, eventListener); - } - - @SuppressWarnings("unchecked") - @Override - public EventSourceAdapter removeListener(EventListener eventListener) { - - if (eventListener.matches(this.listener)) { - return EMPTY; - } - return null; - } - - @Override - public boolean fireEvent(E event) { - - return fireEvent(event, this.listener); - } - - @Override - public boolean hasListeners() { - - return true; - } - - @Override - public int getListenerCount() { - - return 1; - } - - @SuppressWarnings("unchecked") - @Override - public L getListener(int index) { - - if (index == 0) { - return (L) this.listener.unwrap(); - } - return null; - } - - @Override - public EventListener getRawListener(int index) { - - if (index == 0) { - return this.listener; - } - return null; - } - } - - @SuppressWarnings("unchecked") - private static class Multi> extends EventSourceAdapter { - - private EventListener[] listeners; - - private int listenerCount; - - private boolean locked; - - private Multi(EventListener first, EventListener second) { - - super(); - this.listeners = new EventListener[] { first, second }; - this.listenerCount = 2; - } - - @Override - public EventSourceAdapter addListener(EventListener listener) { - - int oldCapacity = this.listeners.length; - if (this.locked) { - int newCapacity = (this.listenerCount < oldCapacity) ? oldCapacity : (oldCapacity * 3) / 2 + 1; - this.listeners = Arrays.copyOf(this.listeners, newCapacity); - } else if (this.listenerCount == oldCapacity) { - this.listenerCount = WeakEventListener.trim(this.listenerCount, this.listeners); - if (this.listenerCount == oldCapacity) { - int newCapacity = (oldCapacity * 3) / 2 + 1; - this.listeners = Arrays.copyOf(this.listeners, newCapacity); - } - } - this.listeners[this.listenerCount++] = listener; - return this; - } - - @Override - public EventSourceAdapter removeListener(EventListener listener) { - - for (int i = 0; i < this.listenerCount; i++) { - if (listener.matches(this.listeners[i])) { - if (this.listenerCount == 2) { - return new Single<>(this.listeners[1 - i]); - } else { - EventListener[] oldListeners = this.listeners; - if (this.locked) { - this.listeners = new EventListener[this.listeners.length]; - System.arraycopy(oldListeners, 0, this.listeners, 0, i); - } - int remaining = this.listenerCount - i - 1; - if (remaining > 0) { - System.arraycopy(oldListeners, i + 1, this.listeners, i, remaining); - } - this.listenerCount--; - if (!this.locked) { - this.listeners[this.listenerCount] = null; - } - } - return this; - } - } - return null; - } - - @Override - public boolean fireEvent(E event) { - - boolean dispatched = false; - try { - this.locked = true; - for (int i = 0; i < this.listenerCount; i++) { - boolean send = fireEvent(event, this.listeners[i]); - if (send) { - dispatched = true; - } - } - } finally { - this.locked = false; - } - return dispatched; - } - - @Override - public boolean hasListeners() { - - return true; - } - - @Override - public int getListenerCount() { - - return this.listenerCount; - } - - @Override - public L getListener(int index) { - - if ((index >= 0) && (index < this.listenerCount)) { - return (L) this.listeners[index].unwrap(); - } - return null; - } - - @Override - public EventListener getRawListener(int index) { - - if ((index >= 0) && (index < this.listenerCount)) { - return this.listeners[index]; - } - return null; - } - } - -} +/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 */ +package io.github.mmm.event; + +import java.util.Arrays; + +import io.github.mmm.base.exception.ReadOnlyException; +import io.github.mmm.event.impl.WeakEventListener; + +/** + * Adapter for {@link EventSource}. + * + * @param the type of the events to send. + * @param the type of the {@link EventListener listeners}. + * @since 1.0.0 + * @see EventSource + * @see AbstractEventSource + */ +public abstract class EventSourceAdapter> { + + private static final Empty EMPTY = new Empty(); + + private static final ReadOnly READ_ONLY = new ReadOnly(); + + EventSourceAdapter() { + + super(); + } + + /** + * @param listener - see {@link EventSource#addListener(EventListener)}. + * @return this adapter itself or a new instance capable to handle more listeners. + */ + public abstract EventSourceAdapter addListener(EventListener listener); + + /** + * @param listener - see {@link EventSource#removeListener(EventListener)}. + * @return {@code null} if the given {@link EventListener} was not registered and nothing changed, otherwise this + * adapter itself or a new instance capable to handle less listeners. + */ + public abstract EventSourceAdapter removeListener(EventListener listener); + + /** + * @param event the event to {@link EventListener#onEvent(Object) send} to all {@link #addListener(EventListener) + * registered} {@link EventListener}s. + * @return {@code true} if the event has actually been dispatched, {@code false} otherwise (no listener was + * {@link #addListener(EventListener) registered} for the event). + */ + public abstract boolean fireEvent(E event); + + boolean fireEvent(E event, EventListener listener) { + + try { + listener.onEvent(event); + return true; + } catch (Exception e) { + Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); + } + return false; + } + + /** + * @return {@code true} if at least one {@link EventListener} is {@link #addListener(EventListener) registered}, + * {@code false} otherwise. + */ + public boolean hasListeners() { + + return (getListenerCount() > 0); + } + + /** + * @return the number of {@link #addListener(EventListener) registered} {@link EventListener}s. + */ + public abstract int getListenerCount(); + + /** + * @param index the index of the requested {@link EventListener} in the range from {@code 0} to + * {@link #getListenerCount()} - 1. + * @return the requested {@link EventListener} or {@code null} if index is out of bounds. + */ + public abstract L getListener(int index); + + /** + * @param index the index of the requested {@link EventListener} in the range from {@code 0} to + * {@link #getListenerCount()} - 1. + * @return the requested {@link EventListener} or {@code null} if index is out of bounds. + */ + public abstract EventListener getRawListener(int index); + + /** + * @param the type of the events to send. + * @param the type of the {@link EventListener listeners}. + * @return the empty {@link EventSourceAdapter}. Is used as a singleton instance for best efficiency allocating + * absolutely no resources until a listener gets {@link #addListener(EventListener) added}. + */ + @SuppressWarnings("unchecked") + public static > EventSourceAdapter empty() { + + return EMPTY; + } + + /** + * Like {@link #empty()} but for read-only objects. + * + * @param the type of the events to send. + * @param the type of the {@link EventListener listeners}. + * @return the read-only {@link EventSourceAdapter}. It will always be empty and never allow any mutation so + * {@link #addListener(EventListener)} will always throw {@link ReadOnlyException}. + */ + @SuppressWarnings("unchecked") + public static > EventSourceAdapter readOnly() { + + return READ_ONLY; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static class Empty extends EventSourceAdapter { + + private Empty() { + + super(); + } + + @Override + public EventSourceAdapter addListener(EventListener listener) { + + return new Single<>(listener); + } + + @Override + public EventSourceAdapter removeListener(EventListener listener) { + + return null; + } + + @Override + public boolean fireEvent(Object event) { + + return false; + } + + @Override + public boolean hasListeners() { + + return false; + } + + @Override + public int getListenerCount() { + + return 0; + } + + @Override + public EventListener getListener(int index) { + + return null; + } + + @Override + public EventListener getRawListener(int index) { + + return null; + } + + } + + private static class Single> extends EventSourceAdapter { + + private final EventListener listener; + + private Single(EventListener listener) { + + super(); + this.listener = listener; + } + + @Override + public EventSourceAdapter addListener(EventListener eventListener) { + + return new Multi<>(this.listener, eventListener); + } + + @SuppressWarnings("unchecked") + @Override + public EventSourceAdapter removeListener(EventListener eventListener) { + + if (eventListener.matches(this.listener)) { + return EMPTY; + } + return null; + } + + @Override + public boolean fireEvent(E event) { + + return fireEvent(event, this.listener); + } + + @Override + public boolean hasListeners() { + + return true; + } + + @Override + public int getListenerCount() { + + return 1; + } + + @SuppressWarnings("unchecked") + @Override + public L getListener(int index) { + + if (index == 0) { + return (L) this.listener.unwrap(); + } + return null; + } + + @Override + public EventListener getRawListener(int index) { + + if (index == 0) { + return this.listener; + } + return null; + } + } + + @SuppressWarnings("unchecked") + private static class Multi> extends EventSourceAdapter { + + private EventListener[] listeners; + + private int listenerCount; + + private boolean locked; + + private Multi(EventListener first, EventListener second) { + + super(); + this.listeners = new EventListener[] { first, second }; + this.listenerCount = 2; + } + + @Override + public EventSourceAdapter addListener(EventListener listener) { + + int oldCapacity = this.listeners.length; + if (this.locked) { + int newCapacity = (this.listenerCount < oldCapacity) ? oldCapacity : (oldCapacity * 3) / 2 + 1; + this.listeners = Arrays.copyOf(this.listeners, newCapacity); + } else if (this.listenerCount == oldCapacity) { + this.listenerCount = WeakEventListener.trim(this.listenerCount, this.listeners); + if (this.listenerCount == oldCapacity) { + int newCapacity = (oldCapacity * 3) / 2 + 1; + this.listeners = Arrays.copyOf(this.listeners, newCapacity); + } + } + this.listeners[this.listenerCount++] = listener; + return this; + } + + @Override + public EventSourceAdapter removeListener(EventListener listener) { + + for (int i = 0; i < this.listenerCount; i++) { + if (listener.matches(this.listeners[i])) { + if (this.listenerCount == 2) { + return new Single<>(this.listeners[1 - i]); + } else { + EventListener[] oldListeners = this.listeners; + if (this.locked) { + this.listeners = new EventListener[this.listeners.length]; + System.arraycopy(oldListeners, 0, this.listeners, 0, i); + } + int remaining = this.listenerCount - i - 1; + if (remaining > 0) { + System.arraycopy(oldListeners, i + 1, this.listeners, i, remaining); + } + this.listenerCount--; + if (!this.locked) { + this.listeners[this.listenerCount] = null; + } + } + return this; + } + } + return null; + } + + @Override + public boolean fireEvent(E event) { + + boolean dispatched = false; + try { + this.locked = true; + for (int i = 0; i < this.listenerCount; i++) { + boolean send = fireEvent(event, this.listeners[i]); + if (send) { + dispatched = true; + } + } + } finally { + this.locked = false; + } + return dispatched; + } + + @Override + public boolean hasListeners() { + + return true; + } + + @Override + public int getListenerCount() { + + return this.listenerCount; + } + + @Override + public L getListener(int index) { + + if ((index >= 0) && (index < this.listenerCount)) { + return (L) this.listeners[index].unwrap(); + } + return null; + } + + @Override + public EventListener getRawListener(int index) { + + if ((index >= 0) && (index < this.listenerCount)) { + return this.listeners[index]; + } + return null; + } + } + + @SuppressWarnings("rawtypes") + private static class ReadOnly extends EventSourceAdapter { + + private ReadOnly() { + + super(); + } + + @Override + public EventSourceAdapter addListener(EventListener listener) { + + throw new ReadOnlyException(EventSourceAdapter.class); + } + + @Override + public EventSourceAdapter removeListener(EventListener listener) { + + return null; + } + + @Override + public boolean fireEvent(Object event) { + + return false; + } + + @Override + public boolean hasListeners() { + + return false; + } + + @Override + public int getListenerCount() { + + return 0; + } + + @Override + public EventListener getListener(int index) { + + return null; + } + + @Override + public EventListener getRawListener(int index) { + + return null; + } + + } + +}