This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push: new 6e2741da4 [LANG-1727] EventListenerSupport doesn't document ordering of events new b02333396 Merge branch 'master' of https://garydgreg...@github.com/apache/commons-lang.git 6e2741da4 is described below commit 6e2741da4d1ef471c7c552b91409b2deecbfcec9 Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Thu Jun 19 07:35:45 2025 -0400 [LANG-1727] EventListenerSupport doesn't document ordering of events - Javadoc - Use longer lines - Remove some vertical whitespace --- src/changes/changes.xml | 1 + .../commons/lang3/event/EventListenerSupport.java | 97 ++++++++++------------ .../lang3/event/EventListenerSupportTest.java | 34 +++----- 3 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9bfba7512..0d52348fd 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -94,6 +94,7 @@ The <action> type attribute can be add,update,fix,remove. <action issue="LANG-1772" type="fix" dev="ggregory" due-to="Gary Gregory">Reimplement org.apache.commons.lang3.ClassUtils.hierarchy(Class, Interfaces) using an AtomicReference.</action> <action type="fix" dev="ggregory" due-to="Ken Dombeck">Fix Javadoc code examples in DiffBuilder and ReflectionDiffBuilder #1400.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix generics in org.apache.commons.lang3.stream.Streams.toArray(Class) signature.</action> + <action issue="LANG-1727" type="fix" dev="ggregory" due-to="Elliotte Rusty Harold, Gary Gregory">EventListenerSupport doesn't document ordering of events.</action> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add Strings and refactor StringUtils.</action> <action issue="LANG-1747" type="add" dev="ggregory" due-to="Oliver B. Fischer, Gary Gregory">Add StopWatch.run([Failable]Runnable) and get([Failable]Supplier).</action> diff --git a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java index 303d2583e..6d67bfc8c 100644 --- a/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java +++ b/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java @@ -37,32 +37,28 @@ import org.apache.commons.lang3.function.FailableConsumer; /** - * An EventListenerSupport object can be used to manage a list of event - * listeners of a particular type. The class provides - * {@link #addListener(Object)} and {@link #removeListener(Object)} methods - * for registering listeners, as well as a {@link #fire()} method for firing - * events to the listeners. + * Manages a list of event listeners of a given generic type. This class provides {@link #addListener(Object)} and {@link #removeListener(Object)} methods for + * managing listeners, as well as a {@link #fire()} method for firing events to the listeners. * * <p> - * To use this class, suppose you want to support ActionEvents. You would do: + * For example, to support ActionEvents: * </p> + * * <pre>{@code - * public class MyActionEventSource - * { - * private EventListenerSupport<ActionListener> actionListeners = - * EventListenerSupport.create(ActionListener.class); + * public class MyActionEventSource { + * + * private EventListenerSupport<ActionListener> actionListeners = EventListenerSupport.create(ActionListener.class); * - * public void someMethodThatFiresAction() - * { - * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool"); - * actionListeners.fire().actionPerformed(e); - * } + * public void someMethodThatFiresAction() { + * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "something"); + * actionListeners.fire().actionPerformed(e); + * } * } * }</pre> - * * <p> - * Serializing an {@link EventListenerSupport} instance will result in any - * non-{@link Serializable} listeners being silently dropped. + * Events are fired + * <p> + * Serializing an {@link EventListenerSupport} instance will result in any non-{@link Serializable} listeners being silently dropped. * </p> * * @param <L> the type of event listener that is supported by this proxy. @@ -71,7 +67,7 @@ public class EventListenerSupport<L> implements Serializable { /** - * An invocation handler used to dispatch the event(s) to all the listeners. + * Invokes listeners through {@link #invoke(Object, Method, Object[])} in the order added to the underlying {@link List}. */ protected class ProxyInvocationHandler implements InvocationHandler { @@ -109,6 +105,9 @@ protected void handle(final Throwable t) throws IllegalAccessException, IllegalA /** * Propagates the method call to all registered listeners in place of the proxy listener object. + * <p> + * Calls listeners in the order added to the underlying {@link List}. + * </p> * * @param unusedProxy the proxy object representing a listener on which the invocation was called; not used * @param method the listener method that will be called on all of the listeners. @@ -156,18 +155,14 @@ public static <T> EventListenerSupport<T> create(final Class<T> listenerInterfac } /** - * The list used to hold the registered listeners. This list is - * intentionally a thread-safe copy-on-write-array so that traversals over - * the list of listeners will be atomic. + * Hold the registered listeners. This list is intentionally a thread-safe copy-on-write-array so that traversals over the list of listeners will be atomic. */ private List<L> listeners = new CopyOnWriteArrayList<>(); /** - * The proxy representing the collection of listeners. Calls to this proxy - * object will be sent to all registered listeners. + * The proxy representing the collection of listeners. Calls to this proxy object will be sent to all registered listeners. */ private transient L proxy; - /** * Empty typed array for #getListeners(). */ @@ -175,13 +170,15 @@ public static <T> EventListenerSupport<T> create(final Class<T> listenerInterfac /** * Constructs a new EventListenerSupport instance. - * Serialization-friendly constructor. + * <p> + * This constructor is needed for serialization. + * </p> */ private EventListenerSupport() { } /** - * Creates an EventListenerSupport object which supports the provided + * Constructs an EventListenerSupport object which supports the provided * listener interface. * * @param listenerInterface the type of listener interface that will receive @@ -197,7 +194,7 @@ public EventListenerSupport(final Class<L> listenerInterface) { } /** - * Creates an EventListenerSupport object which supports the provided + * Constructs an EventListenerSupport object which supports the provided * listener interface using the specified class loader to create the JDK * dynamic proxy. * @@ -212,29 +209,31 @@ public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader this(); Objects.requireNonNull(listenerInterface, "listenerInterface"); Objects.requireNonNull(classLoader, "classLoader"); - Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface", - listenerInterface.getName()); + Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface", listenerInterface.getName()); initializeTransientFields(listenerInterface, classLoader); } /** - * Registers an event listener. + * Adds an event listener. + * <p> + * Listeners are called in the order added. + * </p> * * @param listener the event listener (may not be {@code null}). - * @throws NullPointerException if {@code listener} is - * {@code null}. + * @throws NullPointerException if {@code listener} is {@code null}. */ public void addListener(final L listener) { addListener(listener, true); } /** - * Registers an event listener. Will not add a pre-existing listener - * object to the list if {@code allowDuplicate} is false. + * Adds an event listener. Will not add a pre-existing listener object to the list if {@code allowDuplicate} is false. + * <p> + * Listeners are called in the order added. + * </p> * - * @param listener the event listener (may not be {@code null}). - * @param allowDuplicate the flag for determining if duplicate listener - * objects are allowed to be registered. + * @param listener the event listener (may not be {@code null}). + * @param allowDuplicate the flag for determining if duplicate listener objects are allowed to be registered. * * @throws NullPointerException if {@code listener} is {@code null}. * @since 3.5 @@ -247,7 +246,7 @@ public void addListener(final L listener, final boolean allowDuplicate) { } /** - * Creates the {@link InvocationHandler} responsible for broadcasting calls + * Creates the {@link InvocationHandler} responsible for calling * to the managed listeners. Subclasses can override to provide custom behavior. * * @return ProxyInvocationHandler @@ -263,8 +262,7 @@ protected InvocationHandler createInvocationHandler() { * @param classLoader the class loader to be used */ private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) { - proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, - new Class[] { listenerInterface }, createInvocationHandler())); + proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, new Class[] { listenerInterface }, createInvocationHandler())); } /** @@ -311,7 +309,7 @@ private void initializeTransientFields(final Class<L> listenerInterface, final C } /** - * Deserializes. + * Deserializes the next object into this instance. * * @param objectInputStream the input stream * @throws IOException if an IO error occurs @@ -326,26 +324,25 @@ private void readObject(final ObjectInputStream objectInputStream) throws IOExce } /** - * Unregisters an event listener. + * Removes an event listener. * * @param listener the event listener (may not be {@code null}). * @throws NullPointerException if {@code listener} is * {@code null}. */ public void removeListener(final L listener) { - Objects.requireNonNull(listener, "listener"); - listeners.remove(listener); + listeners.remove(Objects.requireNonNull(listener, "listener")); } /** - * Serializes. + * Serializes this instance onto the given ObjectOutputStream. * * @param objectOutputStream the output stream * @throws IOException if an IO error occurs */ private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException { final ArrayList<L> serializableListeners = new ArrayList<>(); - // don't just rely on instanceof Serializable: + // Don't just rely on instanceof Serializable: ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); for (final L listener : listeners) { try { @@ -356,10 +353,8 @@ private void writeObject(final ObjectOutputStream objectOutputStream) throws IOE testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); } } - /* - * we can reconstitute everything we need from an array of our listeners, - * which has the additional advantage of typically requiring less storage than a list: - */ + // We can reconstitute everything we need from an array of our listeners, + // which has the additional advantage of typically requiring less storage than a list: objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray)); } } diff --git a/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java b/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java index ac310b315..dfa56ccae 100644 --- a/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java +++ b/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java @@ -47,11 +47,13 @@ import org.junit.jupiter.api.Test; /** + * Tests {@link EventListenerSupport}. */ class EventListenerSupportTest extends AbstractLangTest { private void addDeregisterListener(final EventListenerSupport<VetoableChangeListener> listenerSupport) { listenerSupport.addListener(new VetoableChangeListener() { + @Override public void vetoableChange(final PropertyChangeEvent e) { listenerSupport.removeListener(this); @@ -61,6 +63,7 @@ public void vetoableChange(final PropertyChangeEvent e) { private VetoableChangeListener createListener(final List<VetoableChangeListener> calledListeners) { return new VetoableChangeListener() { + @Override public void vetoableChange(final PropertyChangeEvent e) { calledListeners.add(this); @@ -71,14 +74,12 @@ public void vetoableChange(final PropertyChangeEvent e) { @Test void testAddListenerNoDuplicates() { final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class); - final VetoableChangeListener[] listeners = listenerSupport.getListeners(); assertEquals(0, listeners.length); assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType()); final VetoableChangeListener[] empty = listeners; - //for fun, show that the same empty instance is used + // for fun, show that the same empty instance is used assertSame(empty, listenerSupport.getListeners()); - final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class); listenerSupport.addListener(listener1); assertEquals(1, listenerSupport.getListeners().length); @@ -108,7 +109,6 @@ void testCreateWithNullParameter() { void testEventDispatchOrder() throws PropertyVetoException { final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class); final List<VetoableChangeListener> calledListeners = new ArrayList<>(); - final VetoableChangeListener listener1 = createListener(calledListeners); final VetoableChangeListener listener2 = createListener(calledListeners); listenerSupport.addListener(listener1); @@ -122,14 +122,12 @@ void testEventDispatchOrder() throws PropertyVetoException { @Test void testGetListeners() { final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class); - final VetoableChangeListener[] listeners = listenerSupport.getListeners(); assertEquals(0, listeners.length); assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType()); final VetoableChangeListener[] empty = listeners; - //for fun, show that the same empty instance is used + // for fun, show that the same empty instance is used assertSame(empty, listenerSupport.getListeners()); - final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class); listenerSupport.addListener(listener1); assertEquals(1, listenerSupport.getListeners().length); @@ -164,47 +162,42 @@ void testSerialization() throws IOException, ClassNotFoundException, PropertyVet final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class); listenerSupport.addListener(Function.identity()::apply); listenerSupport.addListener(EasyMock.createNiceMock(VetoableChangeListener.class)); - - //serialize: + // serialize: final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) { objectOutputStream.writeObject(listenerSupport); } - - //deserialize: + // deserialize: @SuppressWarnings("unchecked") - final - EventListenerSupport<VetoableChangeListener> deserializedListenerSupport = (EventListenerSupport<VetoableChangeListener>) new ObjectInputStream( + final EventListenerSupport<VetoableChangeListener> deserializedListenerSupport = (EventListenerSupport<VetoableChangeListener>) new ObjectInputStream( new ByteArrayInputStream(outputStream.toByteArray())).readObject(); - - //make sure we get a listener array back, of the correct component type, and that it contains only the serializable mock + // make sure we get a listener array back, of the correct component type, and that it contains only the serializable mock final VetoableChangeListener[] listeners = deserializedListenerSupport.getListeners(); assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType()); assertEquals(1, listeners.length); - - //now verify that the mock still receives events; we can infer that the proxy was correctly reconstituted + // now verify that the mock still receives events; we can infer that the proxy was correctly reconstituted final VetoableChangeListener listener = listeners[0]; final PropertyChangeEvent evt = new PropertyChangeEvent(new Date(), "Day", 7, 9); listener.vetoableChange(evt); EasyMock.replay(listener); deserializedListenerSupport.fire().vetoableChange(evt); EasyMock.verify(listener); - - //remove listener and verify we get an empty array of listeners + // remove listener and verify we get an empty array of listeners deserializedListenerSupport.removeListener(listener); assertEquals(0, deserializedListenerSupport.getListeners().length); } @Test void testSubclassInvocationHandling() throws PropertyVetoException { - final EventListenerSupport<VetoableChangeListener> eventListenerSupport = new EventListenerSupport<VetoableChangeListener>( VetoableChangeListener.class) { + private static final long serialVersionUID = 1L; @Override protected java.lang.reflect.InvocationHandler createInvocationHandler() { return new ProxyInvocationHandler() { + @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { @@ -214,7 +207,6 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg }; } }; - final VetoableChangeListener listener = EasyMock.createNiceMock(VetoableChangeListener.class); eventListenerSupport.addListener(listener); final Object source = new Date();