diff options
8 files changed, 113 insertions, 39 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index be04a7247a..fda12e0a2d 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -120,6 +120,7 @@ <li>Error indicators are now <tt><span class="v-errorindicator"></span></tt> elements.</li> <li><tt>Embedded</tt> is not a <tt>LegacyComponent</tt> anymore.</li> <li><tt>Notification</tt> method <tt>show</tt> returns <tt>Notification</tt>, instead of <tt>void</tt>.</li> + <li><tt>SharedState</tt> field <tt>registeredEventListeners</tt> is a <tt>Map</tt> instead of <tt>Set</tt>.</li> <li>The client side <tt>SelectionModel</tt> interface has a new method <tt>isMultiSelectionAllowed</tt>.</li> <h2>For incompatible or behavior-altering changes in 8.1, please see <a href="https://vaadin.com/download/release/8.1/8.1.0/release-notes.html#incompatible">8.1 release notes</a></h2> diff --git a/client/src/main/java/com/vaadin/client/ui/AbstractConnector.java b/client/src/main/java/com/vaadin/client/ui/AbstractConnector.java index 7269a03df6..849f977b53 100644 --- a/client/src/main/java/com/vaadin/client/ui/AbstractConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/AbstractConnector.java @@ -20,7 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Map; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.dom.client.Element; @@ -498,8 +498,8 @@ public abstract class AbstractConnector */ @Override public boolean hasEventListener(String eventIdentifier) { - Set<String> reg = getState().registeredEventListeners; - return (reg != null && reg.contains(eventIdentifier)); + Map<String, Integer> reg = getState().registeredEventListeners; + return reg != null && reg.containsKey(eventIdentifier); } /** diff --git a/server/src/main/java/com/vaadin/event/EventRouter.java b/server/src/main/java/com/vaadin/event/EventRouter.java index a6bb030cf8..9b2ac0c187 100644 --- a/server/src/main/java/com/vaadin/event/EventRouter.java +++ b/server/src/main/java/com/vaadin/event/EventRouter.java @@ -28,6 +28,8 @@ import java.util.Objects; import com.vaadin.server.ErrorEvent; import com.vaadin.server.ErrorHandler; import com.vaadin.shared.Registration; +import com.vaadin.shared.communication.SharedState; +import com.vaadin.shared.ui.ComponentStateUtil; /** * <code>EventRouter</code> class implementing the inheritable event listening @@ -63,6 +65,67 @@ public class EventRouter implements MethodEventSource { return () -> listenerList.remove(listenerMethod); } + /** + * Registers a new event listener with the specified activation method to + * listen events generated by this component. If the activation method does + * not have any arguments the event object will not be passed to it when + * it's called. + * + * <p> + * This method additionally informs the event-api to stop routing events + * with the given {@code eventIdentifier} to the components handleEvent + * function call. + * </p> + * + * <p> + * The only way to remove the listener is to use the returned + * {@link Registration}. The other methods, e.g. + * {@link #removeAllListeners()} do not do that. + * </p> + * + * <p> + * For more information on the inheritable event mechanism see the + * {@link com.vaadin.event com.vaadin.event package documentation}. + * </p> + * + * @param eventType + * the type of the listened event. Events of this type or its + * subclasses activate the listener. + * @param target + * the object instance who owns the activation method. + * @param method + * the activation method. + * @param eventIdentifier + * the identifier of the event to listen for + * @param state + * The component State + * @return a registration object for removing the listener + * @throws IllegalArgumentException + * unless {@code method} has exactly one match in {@code target} + * @throws NullPointerException + * if {@code target} is {@code null} + * @since + */ + public Registration addListener(Class<?> eventType, Object target, + Method method, String eventIdentifier, SharedState state) { + Objects.requireNonNull(target, "Listener must not be null."); + if (listenerList == null) { + listenerList = new LinkedHashSet<>(); + } + ListenerMethod listenerMethod = new ListenerMethod(eventType, target, + method); + listenerList.add(listenerMethod); + + Registration registration = ComponentStateUtil + .addRegisteredEventListener(state, eventIdentifier); + + return () -> { + registration.remove(); + + listenerList.remove(listenerMethod); + }; + } + /* * Registers a new listener with the specified named activation method to * listen events generated by this component. Don't add a JavaDoc comment diff --git a/server/src/main/java/com/vaadin/event/MethodEventSource.java b/server/src/main/java/com/vaadin/event/MethodEventSource.java index fd4660184c..92d371bcb0 100644 --- a/server/src/main/java/com/vaadin/event/MethodEventSource.java +++ b/server/src/main/java/com/vaadin/event/MethodEventSource.java @@ -37,12 +37,10 @@ import com.vaadin.shared.Registration; public interface MethodEventSource extends Serializable { /** - * <p> * Registers a new event listener with the specified activation method to * listen events generated by this component. If the activation method does * not have any arguments the event object will not be passed to it when * it's called. - * </p> * * <p> * For more information on the inheritable event mechanism see the @@ -68,12 +66,10 @@ public interface MethodEventSource extends Serializable { Method method); /** - * <p> * Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. - * </p> * * <p> * This version of <code>addListener</code> gets the name of the activation @@ -151,11 +147,9 @@ public interface MethodEventSource extends Serializable { Method method); /** - * <p> * Removes one registered listener method. The given method owned by the * given object will no longer be called when the specified events are * generated by this component. - * </p> * * <p> * This version of <code>removeListener</code> gets the name of the diff --git a/server/src/main/java/com/vaadin/server/AbstractClientConnector.java b/server/src/main/java/com/vaadin/server/AbstractClientConnector.java index 75f5d27b4f..23311dface 100644 --- a/server/src/main/java/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/main/java/com/vaadin/server/AbstractClientConnector.java @@ -723,12 +723,10 @@ public abstract class AbstractClientConnector /* Listener code starts. Should be refactored. */ /** - * <p> * Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. - * </p> * * <p> * This method additionally informs the event-api to route events with the @@ -757,16 +755,8 @@ public abstract class AbstractClientConnector if (eventRouter == null) { eventRouter = new EventRouter(); } - boolean needRepaint = !eventRouter.hasListeners(eventType); - Registration registration = eventRouter.addListener(eventType, target, - method); - - if (needRepaint) { - ComponentStateUtil.addRegisteredEventListener(getState(), - eventIdentifier); - } - - return registration; + return eventRouter.addListener(eventType, target, method, + eventIdentifier, getState()); } /** @@ -789,8 +779,8 @@ public abstract class AbstractClientConnector * * <p> * This method additionally informs the event-api to stop routing events - * with the given eventIdentifier to the components handleEvent function - * call. + * with the given {@code eventIdentifier} to the components handleEvent + * function call. * </p> * * <p> @@ -824,12 +814,10 @@ public abstract class AbstractClientConnector } /** - * <p> * Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. - * </p> * * <p> * For more information on the inheritable event mechanism see the @@ -855,12 +843,10 @@ public abstract class AbstractClientConnector } /** - * <p> * Convenience method for registering a new listener with the specified * activation method to listen events generated by this component. If the * activation method does not have any arguments the event object will not * be passed to it when it's called. - * </p> * * <p> * This version of <code>addListener</code> gets the name of the activation diff --git a/server/src/test/java/com/vaadin/tests/event/EventRouterTest.java b/server/src/test/java/com/vaadin/tests/event/EventRouterTest.java index 013c8b8f58..dda158b2d1 100644 --- a/server/src/test/java/com/vaadin/tests/event/EventRouterTest.java +++ b/server/src/test/java/com/vaadin/tests/event/EventRouterTest.java @@ -16,6 +16,8 @@ package com.vaadin.tests.event; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.lang.reflect.Method; @@ -25,7 +27,10 @@ import org.junit.Before; import org.junit.Test; import com.vaadin.event.EventRouter; +import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.server.ErrorHandler; +import com.vaadin.shared.Registration; +import com.vaadin.shared.communication.SharedState; import com.vaadin.ui.Component; import com.vaadin.ui.Component.Listener; import com.vaadin.util.ReflectTools; @@ -109,4 +114,15 @@ public class EventRouterTest { router.fireEvent(new Component.Event(component), errorHandler); EasyMock.verify(listener, listener2, errorHandler); } + + @Test + public void registrationToRemoveRegisteredEventListener() { + SharedState state = new SharedState(); + Listener listener2 = EasyMock.createMock(Component.Listener.class); + Registration registration = router.addListener(ClickEvent.class, + listener2, COMPONENT_EVENT_METHOD, "click", state); + assertTrue(!state.registeredEventListeners.isEmpty()); + registration.remove(); + assertNull(state.registeredEventListeners); + } } diff --git a/shared/src/main/java/com/vaadin/shared/communication/SharedState.java b/shared/src/main/java/com/vaadin/shared/communication/SharedState.java index 6b9c1045c5..57ad67a0d7 100644 --- a/shared/src/main/java/com/vaadin/shared/communication/SharedState.java +++ b/shared/src/main/java/com/vaadin/shared/communication/SharedState.java @@ -19,7 +19,6 @@ package com.vaadin.shared.communication; import java.io.Serializable; import java.util.HashMap; import java.util.Map; -import java.util.Set; import com.vaadin.shared.Connector; import com.vaadin.shared.annotations.NoLayout; @@ -62,10 +61,14 @@ public class SharedState implements Serializable { public Map<String, URLReference> resources = new HashMap<>(); public boolean enabled = true; + /** - * A set of event identifiers with registered listeners. + * A Map of event identifiers with registered listeners, {@code key} is + * event identifier, {@code value} is the listeners count. + * + * @since */ @NoLayout - public Set<String> registeredEventListeners = null; + public Map<String, Integer> registeredEventListeners; } diff --git a/shared/src/main/java/com/vaadin/shared/ui/ComponentStateUtil.java b/shared/src/main/java/com/vaadin/shared/ui/ComponentStateUtil.java index 0ef247353b..a2f6618b9e 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/ComponentStateUtil.java +++ b/shared/src/main/java/com/vaadin/shared/ui/ComponentStateUtil.java @@ -16,7 +16,7 @@ package com.vaadin.shared.ui; import java.io.Serializable; -import java.util.HashSet; +import java.util.HashMap; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.Registration; @@ -67,12 +67,19 @@ public final class ComponentStateUtil implements Serializable { @Deprecated public static final void removeRegisteredEventListener(SharedState state, String eventIdentifier) { - if (state.registeredEventListeners == null) { - return; - } - state.registeredEventListeners.remove(eventIdentifier); - if (state.registeredEventListeners.size() == 0) { - state.registeredEventListeners = null; + if (state.registeredEventListeners != null) { + Integer count = state.registeredEventListeners.get(eventIdentifier); + if (count != null) { + if (count > 1) { + state.registeredEventListeners.put(eventIdentifier, + count - 1); + } else { + state.registeredEventListeners.remove(eventIdentifier); + if (state.registeredEventListeners.isEmpty()) { + state.registeredEventListeners = null; + } + } + } } } @@ -87,9 +94,13 @@ public final class ComponentStateUtil implements Serializable { public static final Registration addRegisteredEventListener( SharedState state, String eventListenerId) { if (state.registeredEventListeners == null) { - state.registeredEventListeners = new HashSet<>(); + state.registeredEventListeners = new HashMap<>(); + } + Integer count = state.registeredEventListeners.get(eventListenerId); + if (count == null) { + count = 0; } - state.registeredEventListeners.add(eventListenerId); + state.registeredEventListeners.put(eventListenerId, count + 1); return () -> removeRegisteredEventListener(state, eventListenerId); } } |