1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.lang3.event;
19
20 import static org.apache.commons.lang3.LangAssertions.assertIllegalArgumentException;
21 import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
22 import static org.junit.jupiter.api.Assertions.assertEquals;
23 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
24 import static org.junit.jupiter.api.Assertions.assertSame;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26
27 import java.beans.PropertyChangeEvent;
28 import java.beans.PropertyVetoException;
29 import java.beans.VetoableChangeListener;
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.ObjectInputStream;
34 import java.io.ObjectOutputStream;
35 import java.lang.reflect.InvocationHandler;
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38 import java.lang.reflect.UndeclaredThrowableException;
39 import java.util.ArrayList;
40 import java.util.Date;
41 import java.util.List;
42 import java.util.concurrent.atomic.AtomicInteger;
43 import java.util.function.Function;
44
45 import org.apache.commons.lang3.AbstractLangTest;
46 import org.apache.commons.lang3.exception.ExceptionUtils;
47 import org.apache.commons.lang3.function.FailableConsumer;
48 import org.easymock.EasyMock;
49 import org.junit.jupiter.api.Test;
50
51
52
53
54 class EventListenerSupportTest extends AbstractLangTest {
55
56 private void addDeregisterListener(final EventListenerSupport<VetoableChangeListener> listenerSupport) {
57 listenerSupport.addListener(new VetoableChangeListener() {
58
59 @Override
60 public void vetoableChange(final PropertyChangeEvent e) {
61 listenerSupport.removeListener(this);
62 }
63 });
64 }
65
66 private VetoableChangeListener createListener(final List<VetoableChangeListener> calledListeners) {
67 return new VetoableChangeListener() {
68
69 @Override
70 public void vetoableChange(final PropertyChangeEvent e) {
71 calledListeners.add(this);
72 }
73 };
74 }
75
76 @Test
77 void testAddListenerNoDuplicates() {
78 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
79 final VetoableChangeListener[] listeners = listenerSupport.getListeners();
80 assertEquals(0, listeners.length);
81 assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
82 final VetoableChangeListener[] empty = listeners;
83
84 assertSame(empty, listenerSupport.getListeners());
85 final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
86 listenerSupport.addListener(listener1);
87 assertEquals(1, listenerSupport.getListeners().length);
88 listenerSupport.addListener(listener1, false);
89 assertEquals(1, listenerSupport.getListeners().length);
90 listenerSupport.removeListener(listener1);
91 assertSame(empty, listenerSupport.getListeners());
92 }
93
94 @Test
95 void testAddNullListener() {
96 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
97 assertNullPointerException(() -> listenerSupport.addListener(null));
98 }
99
100 @Test
101 void testCreateWithNonInterfaceParameter() {
102 assertIllegalArgumentException(() -> EventListenerSupport.create(String.class));
103 }
104
105 @Test
106 void testCreateWithNullParameter() {
107 assertNullPointerException(() -> EventListenerSupport.create(null));
108 }
109
110 @Test
111 void testEventDispatchOrder() throws PropertyVetoException {
112 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
113 final List<VetoableChangeListener> calledListeners = new ArrayList<>();
114 final VetoableChangeListener listener1 = createListener(calledListeners);
115 final VetoableChangeListener listener2 = createListener(calledListeners);
116 listenerSupport.addListener(listener1);
117 listenerSupport.addListener(listener2);
118 listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
119 assertEquals(calledListeners.size(), 2);
120 assertSame(calledListeners.get(0), listener1);
121 assertSame(calledListeners.get(1), listener2);
122 }
123
124 @Test
125 void testGetListeners() {
126 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
127 final VetoableChangeListener[] listeners = listenerSupport.getListeners();
128 assertEquals(0, listeners.length);
129 assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
130 final VetoableChangeListener[] empty = listeners;
131
132 assertSame(empty, listenerSupport.getListeners());
133 final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
134 listenerSupport.addListener(listener1);
135 assertEquals(1, listenerSupport.getListeners().length);
136 final VetoableChangeListener listener2 = EasyMock.createNiceMock(VetoableChangeListener.class);
137 listenerSupport.addListener(listener2);
138 assertEquals(2, listenerSupport.getListeners().length);
139 listenerSupport.removeListener(listener1);
140 assertEquals(1, listenerSupport.getListeners().length);
141 listenerSupport.removeListener(listener2);
142 assertSame(empty, listenerSupport.getListeners());
143 }
144
145 @Test
146 void testRemoveListenerDuringEvent() throws PropertyVetoException {
147 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
148 for (int i = 0; i < 10; ++i) {
149 addDeregisterListener(listenerSupport);
150 }
151 assertEquals(listenerSupport.getListenerCount(), 10);
152 listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
153 assertEquals(listenerSupport.getListenerCount(), 0);
154 }
155
156 @Test
157 void testRemoveNullListener() {
158 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
159 assertNullPointerException(() -> listenerSupport.removeListener(null));
160 }
161
162 @Test
163 void testSerialization() throws IOException, ClassNotFoundException, PropertyVetoException {
164 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
165 listenerSupport.addListener(Function.identity()::apply);
166 listenerSupport.addListener(EasyMock.createNiceMock(VetoableChangeListener.class));
167
168 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
169 try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
170 objectOutputStream.writeObject(listenerSupport);
171 }
172
173 @SuppressWarnings("unchecked")
174 final EventListenerSupport<VetoableChangeListener> deserializedListenerSupport = (EventListenerSupport<VetoableChangeListener>) new ObjectInputStream(
175 new ByteArrayInputStream(outputStream.toByteArray())).readObject();
176
177 final VetoableChangeListener[] listeners = deserializedListenerSupport.getListeners();
178 assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
179 assertEquals(1, listeners.length);
180
181 final VetoableChangeListener listener = listeners[0];
182 final PropertyChangeEvent evt = new PropertyChangeEvent(new Date(), "Day", 7, 9);
183 listener.vetoableChange(evt);
184 EasyMock.replay(listener);
185 deserializedListenerSupport.fire().vetoableChange(evt);
186 EasyMock.verify(listener);
187
188 deserializedListenerSupport.removeListener(listener);
189 assertEquals(0, deserializedListenerSupport.getListeners().length);
190 }
191
192 @Test
193 void testSubclassInvocationHandling() throws PropertyVetoException {
194 final EventListenerSupport<VetoableChangeListener> eventListenerSupport = new EventListenerSupport<VetoableChangeListener>(
195 VetoableChangeListener.class) {
196
197 private static final long serialVersionUID = 1L;
198
199 @Override
200 protected java.lang.reflect.InvocationHandler createInvocationHandler() {
201 return new ProxyInvocationHandler() {
202
203 @Override
204 public Object invoke(final Object proxy, final Method method, final Object[] args)
205 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
206 return "vetoableChange".equals(method.getName()) && "Hour".equals(((PropertyChangeEvent) args[0]).getPropertyName()) ? null
207 : super.invoke(proxy, method, args);
208 }
209 };
210 }
211 };
212 final VetoableChangeListener listener = EasyMock.createNiceMock(VetoableChangeListener.class);
213 eventListenerSupport.addListener(listener);
214 final Object source = new Date();
215 final PropertyChangeEvent ignore = new PropertyChangeEvent(source, "Hour", 5, 6);
216 final PropertyChangeEvent respond = new PropertyChangeEvent(source, "Day", 6, 7);
217 listener.vetoableChange(respond);
218 EasyMock.replay(listener);
219 eventListenerSupport.fire().vetoableChange(ignore);
220 eventListenerSupport.fire().vetoableChange(respond);
221 EasyMock.verify(listener);
222 }
223
224
225
226
227 @Test
228 void testThrowingListener() {
229 final AtomicInteger count = new AtomicInteger();
230 final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
231 final int vetoLimit = 1;
232 final int listenerCount = 10;
233 for (int i = 0; i < listenerCount; ++i) {
234 listenerSupport.addListener(evt -> {
235 if (count.incrementAndGet() > vetoLimit) {
236 throw new PropertyVetoException(count.toString(), evt);
237 }
238 });
239 }
240 assertEquals(listenerCount, listenerSupport.getListenerCount());
241 assertEquals(0, count.get());
242 final Exception e = assertThrows(UndeclaredThrowableException.class,
243 () -> listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 0, 1)));
244 final Throwable rootCause = ExceptionUtils.getRootCause(e);
245 assertInstanceOf(PropertyVetoException.class, rootCause);
246 assertEquals(vetoLimit + 1, count.get());
247 }
248
249
250
251
252 @Test
253 void testThrowingListenerContinues() throws PropertyVetoException {
254 final AtomicInteger count = new AtomicInteger();
255 final EventListenerSupport<VetoableChangeListener> listenerSupport = new EventListenerSupport<VetoableChangeListener>(VetoableChangeListener.class) {
256 @Override
257 protected InvocationHandler createInvocationHandler() {
258 return new ProxyInvocationHandler(FailableConsumer.nop());
259 }
260 };
261 final int vetoLimit = 1;
262 final int listenerCount = 10;
263 for (int i = 0; i < listenerCount; ++i) {
264 listenerSupport.addListener(evt -> {
265 if (count.incrementAndGet() > vetoLimit) {
266 throw new PropertyVetoException(count.toString(), evt);
267 }
268 });
269 }
270 assertEquals(listenerCount, listenerSupport.getListenerCount());
271 assertEquals(0, count.get());
272 listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 0, 1));
273 assertEquals(listenerCount, count.get());
274 }
275
276 }