blob: ca7bbc62708428132654b05cad9618edcf62091a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.commons.configuration2.event;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Test class for {@code EventListenerList.}
*/
public class TestEventListenerList {
/**
* Test event class. For testing purposes, a small hierarchy of test event class is created. This way it can be checked
* whether event types are correctly evaluated and take the event hierarchy into account.
*/
private static class EventBase extends Event {
private static final long serialVersionUID = 1L;
/** An event message for testing pay-load. */
private final String message;
public EventBase(final Object source, final EventType<? extends EventBase> type, final String msg) {
super(source, type);
message = msg;
}
public String getMessage() {
return message;
}
}
/**
* A test event class derived from the base test event class.
*/
private static final class EventSub1 extends EventBase {
private static final long serialVersionUID = 1L;
public EventSub1(final Object source, final EventType<? extends EventSub1> type, final String msg) {
super(source, type, msg);
}
}
/**
* Another test event class derived from the base class.
*/
private static final class EventSub2 extends EventBase {
private static final long serialVersionUID = 1L;
public EventSub2(final Object source, final EventType<? extends EventSub2> type, final String msg) {
super(source, type, msg);
}
}
/**
* A test event listener implementation. This listener class expects that it receives at most a single event. This event
* is stored for further evaluation.
*/
private static final class ListenerTestImpl implements EventListener<EventBase> {
/** The event received by this object. */
private EventBase receivedEvent;
/**
* Checks that this listener has received an event with the expected properties.
*
* @param expSource the expected source
* @param expType the expected type
* @param expMessage the expected message
*/
public void assertEvent(final Object expSource, final EventType<?> expType, final String expMessage) {
assertNotNull(receivedEvent);
assertEquals(expSource, receivedEvent.getSource());
assertEquals(expType, receivedEvent.getEventType());
assertEquals(expMessage, receivedEvent.getMessage());
}
/**
* Checks that this listener has not received any event.
*/
public void assertNoEvent() {
assertNull(receivedEvent);
}
@Override
public void onEvent(final EventBase event) {
assertNull(receivedEvent, "Too many events: " + event);
receivedEvent = event;
}
}
/** Constant for a test event message. */
private static final String MESSAGE = "TestEventMessage";
/** Type for the base event. */
private static EventType<EventBase> typeBase;
/** Type for sub event 1. */
private static EventType<EventSub1> typeSub1;
/** Type for sub event 2. */
private static EventType<EventSub2> typeSub2;
/**
* Helper method for collecting the elements in the given iterable.
*
* @param iterable the iterable
* @return a list with the content of the iterable
*/
private static <T> List<T> fetchElements(final Iterable<? extends T> iterable) {
final List<T> elems = new LinkedList<>();
for (final T listener : iterable) {
elems.add(listener);
}
return elems;
}
@BeforeAll
public static void setUpBeforeClass() throws Exception {
typeBase = new EventType<>(Event.ANY, "BASE");
typeSub1 = new EventType<>(typeBase, "SUB1");
typeSub2 = new EventType<>(typeBase, "SUB2");
}
/** The list to be tested. */
private EventListenerList list;
/**
* Helper method for checking whether a specific set of event listeners is returned by getEventListeners().
*
* @param eventType the event type
* @param expListeners the expected listeners
*/
private void checkEventListenersForType(final EventType<? extends Event> eventType, final EventListener<?>... expListeners) {
final List<?> listeners = fetchElements(list.getEventListeners(eventType));
assertEquals(Arrays.asList(expListeners), listeners);
}
@BeforeEach
public void setUp() throws Exception {
list = new EventListenerList();
}
/**
* Tests whether the content of another list can be added.
*/
@Test
public void testAddAll() {
final EventListener<EventBase> l1 = new ListenerTestImpl();
final EventListener<EventBase> l2 = new ListenerTestImpl();
final EventListener<EventBase> l3 = new ListenerTestImpl();
list.addEventListener(typeBase, l1);
final EventListenerList list2 = new EventListenerList();
list2.addEventListener(typeSub1, l2);
list2.addEventListener(typeBase, l3);
list.addAll(list2);
final Iterator<EventListenerRegistrationData<?>> it = list.getRegistrations().iterator();
EventListenerRegistrationData<?> reg = it.next();
assertEquals(typeBase, reg.getEventType());
assertEquals(l1, reg.getListener());
reg = it.next();
assertEquals(typeSub1, reg.getEventType());
assertEquals(l2, reg.getListener());
reg = it.next();
assertEquals(typeBase, reg.getEventType());
assertEquals(l3, reg.getListener());
}
/**
* Tries to add the content of a null list.
*/
@Test
public void testAddAllNull() {
assertThrows(IllegalArgumentException.class, () -> list.addAll(null));
}
/**
* Tests whether the list can be cleared.
*/
@Test
public void testClear() {
list.addEventListener(typeSub1, new ListenerTestImpl());
list.addEventListener(typeSub2, new ListenerTestImpl());
list.clear();
assertTrue(list.getRegistrations().isEmpty());
}
/**
* Tests that a null event is handled by the iterator.
*/
@Test
public void testEventListenerIteratorNullEvent() {
list.addEventListener(typeBase, new ListenerTestImpl());
final EventListenerList.EventListenerIterator<EventBase> iterator = list.getEventListenerIterator(typeBase);
assertThrows(IllegalArgumentException.class, () -> iterator.invokeNext(null));
}
/**
* Tests whether the event listener iterator validates the passed in event object.
*/
@Test
public void testEventListenerIteratorWrongEvent() {
final EventListener<EventSub2> listener = event -> {};
list.addEventListener(typeSub2, listener);
final EventListenerList.EventListenerIterator<EventSub2> iterator = list.getEventListenerIterator(typeSub2);
assertTrue(iterator.hasNext());
final Event event = new EventBase(this, typeBase, "Test");
assertThrows(IllegalArgumentException.class, () -> iterator.invokeNext(event));
}
/**
* Tests that a null event is rejected by fire().
*/
@Test
public void testFireNullEvent() {
assertThrows(IllegalArgumentException.class, () -> list.fire(null));
}
/**
* Tests whether event listener registrations derived from a super type can be queried.
*/
@Test
public void testGetEventListenerRegistrationsForSuperType() {
final ListenerTestImpl l1 = new ListenerTestImpl();
final ListenerTestImpl l2 = new ListenerTestImpl();
@SuppressWarnings("unchecked")
final EventListener<Event> l3 = mock(EventListener.class);
list.addEventListener(typeSub1, l1);
list.addEventListener(Event.ANY, l3);
list.addEventListener(typeBase, l2);
final List<EventListenerRegistrationData<? extends EventBase>> regs = list.getRegistrationsForSuperType(typeBase);
final Iterator<EventListenerRegistrationData<? extends EventBase>> iterator = regs.iterator();
assertEquals(l1, iterator.next().getListener());
assertEquals(l2, iterator.next().getListener());
assertFalse(iterator.hasNext());
}
/**
* Tests whether the base type is taken into account when querying for event listeners.
*/
@Test
public void testGetEventListenersBaseType() {
final ListenerTestImpl listener1 = new ListenerTestImpl();
final ListenerTestImpl listener2 = new ListenerTestImpl();
list.addEventListener(typeBase, listener1);
list.addEventListener(typeBase, listener2);
checkEventListenersForType(typeSub1, listener1, listener2);
}
/**
* Tests that the iterator returned by getEventListeners() throws an exception if the iteration goes beyond the last
* element.
*/
@Test
public void testGetEventListenersIteratorNextNoElement() {
final ListenerTestImpl listener1 = new ListenerTestImpl();
final ListenerTestImpl listener2 = new ListenerTestImpl();
list.addEventListener(typeBase, listener1);
list.addEventListener(typeBase, listener2);
final Iterator<EventListener<? super EventBase>> iterator = list.getEventListeners(typeBase).iterator();
for (int i = 0; i < 2; i++) {
iterator.next();
}
assertThrows(NoSuchElementException.class, iterator::next);
}
/**
* Tests that the iterator returned by getEventListeners() does not support remove() operations.
*/
@Test
public void testGetEventListenersIteratorRemove() {
list.addEventListener(typeBase, new ListenerTestImpl());
final Iterator<EventListener<? super EventBase>> iterator = list.getEventListeners(typeBase).iterator();
assertTrue(iterator.hasNext());
assertThrows(UnsupportedOperationException.class, iterator::remove);
}
/**
* Tests whether only matching event listeners are returned by getEventListeners().
*/
@Test
public void testGetEventListenersMatchingType() {
final ListenerTestImpl listener1 = new ListenerTestImpl();
final ListenerTestImpl listener2 = new ListenerTestImpl();
list.addEventListener(typeSub1, listener1);
list.addEventListener(typeSub2, listener2);
checkEventListenersForType(typeSub1, listener1);
}
/**
* Tests that an empty result is correctly handled by getEventListeners().
*/
@Test
public void testGetEventListenersNoMatch() {
list.addEventListener(typeSub1, new ListenerTestImpl());
checkEventListenersForType(typeSub2);
}
/**
* Tests whether event listeners for a null type can be queried.
*/
@Test
public void testGetEventListenersNull() {
assertTrue(fetchElements(list.getEventListeners(null)).isEmpty());
}
/**
* Tests whether all event listener registrations can be queried.
*/
@Test
public void testGetRegistrations() {
final EventListenerRegistrationData<EventSub1> reg1 = new EventListenerRegistrationData<>(typeSub1, new ListenerTestImpl());
final EventListenerRegistrationData<EventSub2> reg2 = new EventListenerRegistrationData<>(typeSub2, new ListenerTestImpl());
list.addEventListener(reg1);
list.addEventListener(reg2);
final List<EventListenerRegistrationData<?>> registrations = list.getRegistrations();
assertEquals(Arrays.asList(reg1, reg2), registrations);
}
/**
* Tests that the list with registration information cannot be modified.
*/
@Test
public void testGetRegistrationsModify() {
final EventListenerRegistrationData<EventBase> registrationData = new EventListenerRegistrationData<>(typeBase, new ListenerTestImpl());
final List<EventListenerRegistrationData<?>> registrations = list.getRegistrations();
assertThrows(UnsupportedOperationException.class, () -> registrations.add(registrationData));
}
/**
* Tests whether an event listener can be registered via a registration data object.
*/
@Test
public void testListenerRegistrationWithListenerData() {
final ListenerTestImpl listener = new ListenerTestImpl();
final EventListenerRegistrationData<EventSub1> regData = new EventListenerRegistrationData<>(typeSub1, listener);
list.addEventListener(regData);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener.assertEvent(this, typeSub1, MESSAGE);
}
/**
* Tries to register a listener with a null registration data object.
*/
@Test
public void testListenerRegistrationWithNullListenerData() {
assertThrows(IllegalArgumentException.class, () -> list.addEventListener(null));
}
/**
* Tests that a listener can be registered multiple times for different event types.
*/
@Test
public void testMultipleListenerRegistration() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeSub1, listener);
list.addEventListener(typeSub2, listener);
list.fire(new EventSub2(this, typeSub2, MESSAGE));
list.removeEventListener(typeSub1, listener);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener.assertEvent(this, typeSub2, MESSAGE);
}
/**
* Tests whether the event type is taken into account when calling listeners.
*/
@Test
public void testReceiveEventDifferentType() {
final ListenerTestImpl listener1 = new ListenerTestImpl();
final ListenerTestImpl listener2 = new ListenerTestImpl();
list.addEventListener(typeSub1, listener1);
list.addEventListener(typeSub2, listener2);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener1.assertEvent(this, typeSub1, MESSAGE);
listener2.assertNoEvent();
}
/**
* Tests whether multiple event listeners can be registered.
*/
@Test
public void testReceiveEventMultipleListeners() {
final ListenerTestImpl listener1 = new ListenerTestImpl();
final ListenerTestImpl listener2 = new ListenerTestImpl();
list.addEventListener(typeSub1, listener1);
list.addEventListener(typeSub1, listener2);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener1.assertEvent(this, typeSub1, MESSAGE);
listener2.assertEvent(this, typeSub1, MESSAGE);
}
/**
* Tests whether events matching the registration type are delivered.
*/
@Test
public void testReceiveEventOfExactType() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeSub1, listener);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener.assertEvent(this, typeSub1, MESSAGE);
}
/**
* Tests that events of a derived type are delivered to listeners registered for a base type.
*/
@Test
public void testReceiveEventSubType() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeBase, listener);
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener.assertEvent(this, typeSub1, MESSAGE);
}
/**
* Tries to register a listener for a null event type.
*/
@Test
public void testRegisterEventTypeNull() {
final ListenerTestImpl listener = new ListenerTestImpl();
assertThrows(IllegalArgumentException.class, () -> list.addEventListener(null, listener));
}
/**
* Tests that null event listeners cannot be registered.
*/
@Test
public void testRegisterListenerNull() {
assertThrows(IllegalArgumentException.class, () -> list.addEventListener(typeBase, null));
}
/**
* Tests whether an event listener can be removed.
*/
@Test
public void testRemoveEventListenerExisting() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeSub1, listener);
assertTrue(list.removeEventListener(typeSub1, listener));
list.fire(new EventSub1(this, typeSub1, MESSAGE));
listener.assertNoEvent();
}
/**
* Tests removeEventListener() if another event type is specified for an existing listener.
*/
@Test
public void testRemoveEventListenerNonExistingEventType() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeSub1, listener);
assertFalse(list.removeEventListener(typeBase, listener));
}
/**
* Tests removeEventListener() for a non-existing event listener.
*/
@Test
public void testRemoveEventListenerNonExistingListener() {
list.addEventListener(typeBase, new ListenerTestImpl());
assertFalse(list.removeEventListener(typeBase, new ListenerTestImpl()));
}
/**
* Tests that removeEventListener() can handle a null listener.
*/
@Test
public void testRemoveEventListenerNullListener() {
assertFalse(list.removeEventListener(typeBase, null));
}
/**
* Tests that removeEventListener() can handle a null registration object.
*/
@Test
public void testRemoveEventListenerNullRegistration() {
assertFalse(list.removeEventListener(null));
}
/**
* Tests that removeEventListener() can handle a null event type.
*/
@Test
public void testRemoveEventListenerNullType() {
assertFalse(list.removeEventListener(null, new ListenerTestImpl()));
}
/**
* Tests that events of a base type do not cause a listener to be invoked.
*/
@Test
public void testSuppressEventOfSuperType() {
final ListenerTestImpl listener = new ListenerTestImpl();
list.addEventListener(typeSub1, listener);
list.fire(new EventBase(this, typeBase, MESSAGE));
listener.assertNoEvent();
}
}