View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.lang3.event;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertSame;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.beans.PropertyChangeEvent;
25  import java.beans.PropertyVetoException;
26  import java.beans.VetoableChangeListener;
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.lang.reflect.InvocationTargetException;
33  import java.lang.reflect.Method;
34  import java.util.ArrayList;
35  import java.util.Date;
36  import java.util.List;
37  import java.util.function.Function;
38  
39  import org.apache.commons.lang3.AbstractLangTest;
40  import org.easymock.EasyMock;
41  import org.junit.jupiter.api.Test;
42  
43  /**
44   * @since 3.0
45   */
46  public class EventListenerSupportTest extends AbstractLangTest {
47  
48      private void addDeregisterListener(final EventListenerSupport<VetoableChangeListener> listenerSupport) {
49          listenerSupport.addListener(new VetoableChangeListener() {
50              @Override
51              public void vetoableChange(final PropertyChangeEvent e) {
52                  listenerSupport.removeListener(this);
53              }
54          });
55      }
56  
57      private VetoableChangeListener createListener(final List<VetoableChangeListener> calledListeners) {
58          return new VetoableChangeListener() {
59              @Override
60              public void vetoableChange(final PropertyChangeEvent e) {
61                  calledListeners.add(this);
62              }
63          };
64      }
65  
66      @Test
67      public void testAddListenerNoDuplicates() {
68          final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
69  
70          final VetoableChangeListener[] listeners = listenerSupport.getListeners();
71          assertEquals(0, listeners.length);
72          assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
73          final VetoableChangeListener[] empty = listeners;
74          //for fun, show that the same empty instance is used
75          assertSame(empty, listenerSupport.getListeners());
76  
77          final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
78          listenerSupport.addListener(listener1);
79          assertEquals(1, listenerSupport.getListeners().length);
80          listenerSupport.addListener(listener1, false);
81          assertEquals(1, listenerSupport.getListeners().length);
82          listenerSupport.removeListener(listener1);
83          assertSame(empty, listenerSupport.getListeners());
84      }
85  
86      @Test
87      public void testAddNullListener() {
88          final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
89          assertThrows(NullPointerException.class, () -> listenerSupport.addListener(null));
90      }
91  
92      @Test
93      public void testCreateWithNonInterfaceParameter() {
94          assertThrows(IllegalArgumentException.class, () -> EventListenerSupport.create(String.class));
95      }
96  
97      @Test
98      public void testCreateWithNullParameter() {
99          assertThrows(NullPointerException.class, () -> EventListenerSupport.create(null));
100     }
101 
102     @Test
103     public void testEventDispatchOrder() throws PropertyVetoException {
104         final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
105         final List<VetoableChangeListener> calledListeners = new ArrayList<>();
106 
107         final VetoableChangeListener listener1 = createListener(calledListeners);
108         final VetoableChangeListener listener2 = createListener(calledListeners);
109         listenerSupport.addListener(listener1);
110         listenerSupport.addListener(listener2);
111         listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
112         assertEquals(calledListeners.size(), 2);
113         assertSame(calledListeners.get(0), listener1);
114         assertSame(calledListeners.get(1), listener2);
115     }
116 
117     @Test
118     public void testGetListeners() {
119         final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
120 
121         final VetoableChangeListener[] listeners = listenerSupport.getListeners();
122         assertEquals(0, listeners.length);
123         assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
124         final VetoableChangeListener[] empty = listeners;
125         //for fun, show that the same empty instance is used
126         assertSame(empty, listenerSupport.getListeners());
127 
128         final VetoableChangeListener listener1 = EasyMock.createNiceMock(VetoableChangeListener.class);
129         listenerSupport.addListener(listener1);
130         assertEquals(1, listenerSupport.getListeners().length);
131         final VetoableChangeListener listener2 = EasyMock.createNiceMock(VetoableChangeListener.class);
132         listenerSupport.addListener(listener2);
133         assertEquals(2, listenerSupport.getListeners().length);
134         listenerSupport.removeListener(listener1);
135         assertEquals(1, listenerSupport.getListeners().length);
136         listenerSupport.removeListener(listener2);
137         assertSame(empty, listenerSupport.getListeners());
138     }
139 
140     @Test
141     public void testRemoveListenerDuringEvent() throws PropertyVetoException {
142         final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
143         for (int i = 0; i < 10; ++i) {
144             addDeregisterListener(listenerSupport);
145         }
146         assertEquals(listenerSupport.getListenerCount(), 10);
147         listenerSupport.fire().vetoableChange(new PropertyChangeEvent(new Date(), "Day", 4, 5));
148         assertEquals(listenerSupport.getListenerCount(), 0);
149     }
150 
151     @Test
152     public void testRemoveNullListener() {
153         final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
154         assertThrows(NullPointerException.class, () -> listenerSupport.removeListener(null));
155     }
156 
157     @Test
158     public void testSerialization() throws IOException, ClassNotFoundException, PropertyVetoException {
159         final EventListenerSupport<VetoableChangeListener> listenerSupport = EventListenerSupport.create(VetoableChangeListener.class);
160         listenerSupport.addListener(Function.identity()::apply);
161         listenerSupport.addListener(EasyMock.createNiceMock(VetoableChangeListener.class));
162 
163         //serialize:
164         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
165         try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
166             objectOutputStream.writeObject(listenerSupport);
167         }
168 
169         //deserialize:
170         @SuppressWarnings("unchecked")
171         final
172         EventListenerSupport<VetoableChangeListener> deserializedListenerSupport = (EventListenerSupport<VetoableChangeListener>) new ObjectInputStream(
173                 new ByteArrayInputStream(outputStream.toByteArray())).readObject();
174 
175         //make sure we get a listener array back, of the correct component type, and that it contains only the serializable mock
176         final VetoableChangeListener[] listeners = deserializedListenerSupport.getListeners();
177         assertEquals(VetoableChangeListener.class, listeners.getClass().getComponentType());
178         assertEquals(1, listeners.length);
179 
180         //now verify that the mock still receives events; we can infer that the proxy was correctly reconstituted
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         //remove listener and verify we get an empty array of listeners
189         deserializedListenerSupport.removeListener(listener);
190         assertEquals(0, deserializedListenerSupport.getListeners().length);
191     }
192 
193     @Test
194     public void testSubclassInvocationHandling() throws PropertyVetoException {
195 
196         final EventListenerSupport<VetoableChangeListener> eventListenerSupport = new EventListenerSupport<VetoableChangeListener>(
197                 VetoableChangeListener.class) {
198             private static final long serialVersionUID = 1L;
199 
200             @Override
201             protected java.lang.reflect.InvocationHandler createInvocationHandler() {
202                 return new ProxyInvocationHandler() {
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 
213         final VetoableChangeListener listener = EasyMock.createNiceMock(VetoableChangeListener.class);
214         eventListenerSupport.addListener(listener);
215         final Object source = new Date();
216         final PropertyChangeEvent ignore = new PropertyChangeEvent(source, "Hour", 5, 6);
217         final PropertyChangeEvent respond = new PropertyChangeEvent(source, "Day", 6, 7);
218         listener.vetoableChange(respond);
219         EasyMock.replay(listener);
220         eventListenerSupport.fire().vetoableChange(ignore);
221         eventListenerSupport.fire().vetoableChange(respond);
222         EasyMock.verify(listener);
223     }
224 }