From 3f2fb29d4b3981c58ffd9b517d6feeb5b5635ae7 Mon Sep 17 00:00:00 2001 From: reiern70 Date: Mon, 16 Oct 2023 12:19:53 -0500 Subject: [PATCH] [WICKET-7080] 1) make default events delivery machinery pluggable 2) allow to disabled sending events 3) define reflection based machinery to deliver events --- .../java/org/apache/wicket/Component.java | 8 +- .../org/apache/wicket/ComponentEvent.java | 2 +- .../apache/wicket/event/EventAwareObject.java | 34 + .../java/org/apache/wicket/event/IEvent.java | 5 + .../wicket/event/IEventAwareObject.java | 23 + .../java/org/apache/wicket/event/OnEvent.java | 42 ++ .../org/apache/wicket/event/OnEvents.java | 37 ++ .../event/ReflectionEventDispatcher.java | 131 ++++ .../wicket/settings/FrameworkSettings.java | 63 +- .../event/ReflectionEventDispatcherTest.java | 583 ++++++++++++++++++ 10 files changed, 912 insertions(+), 16 deletions(-) create mode 100644 wicket-core/src/main/java/org/apache/wicket/event/EventAwareObject.java create mode 100644 wicket-core/src/main/java/org/apache/wicket/event/IEventAwareObject.java create mode 100644 wicket-core/src/main/java/org/apache/wicket/event/OnEvent.java create mode 100644 wicket-core/src/main/java/org/apache/wicket/event/OnEvents.java create mode 100644 wicket-core/src/main/java/org/apache/wicket/event/ReflectionEventDispatcher.java create mode 100644 wicket-core/src/test/java/org/apache/wicket/event/ReflectionEventDispatcherTest.java diff --git a/wicket-core/src/main/java/org/apache/wicket/Component.java b/wicket-core/src/main/java/org/apache/wicket/Component.java index ef065d8c288..4a6e3825903 100644 --- a/wicket-core/src/main/java/org/apache/wicket/Component.java +++ b/wicket-core/src/main/java/org/apache/wicket/Component.java @@ -4445,8 +4445,12 @@ public void onEvent(IEvent event) @Override public final void send(IEventSink sink, Broadcast type, T payload) { - new ComponentEventSender(this, getApplication().getFrameworkSettings()).send(sink, type, - payload); + // if there are no event dispatchers then don't even try to send event + if (getApplication().getFrameworkSettings().hasAnyAnyEventDispatchers()) + { + new ComponentEventSender(this, getApplication().getFrameworkSettings()).send(sink, type, + payload); + } } /** diff --git a/wicket-core/src/main/java/org/apache/wicket/ComponentEvent.java b/wicket-core/src/main/java/org/apache/wicket/ComponentEvent.java index d0f613e0ddb..bab29ff4355 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ComponentEvent.java +++ b/wicket-core/src/main/java/org/apache/wicket/ComponentEvent.java @@ -101,7 +101,7 @@ public void stop() stop = true; } - boolean isStop() + public boolean isStop() { return stop; } diff --git a/wicket-core/src/main/java/org/apache/wicket/event/EventAwareObject.java b/wicket-core/src/main/java/org/apache/wicket/event/EventAwareObject.java new file mode 100644 index 00000000000..7214312e9d2 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/event/EventAwareObject.java @@ -0,0 +1,34 @@ +/* + * 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.wicket.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to mark a component, a session or application as aware of annotated events + * + * See {@link ReflectionEventDispatcher} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE }) +public @interface EventAwareObject +{ + +} diff --git a/wicket-core/src/main/java/org/apache/wicket/event/IEvent.java b/wicket-core/src/main/java/org/apache/wicket/event/IEvent.java index 89ae35704ba..6254f24afef 100644 --- a/wicket-core/src/main/java/org/apache/wicket/event/IEvent.java +++ b/wicket-core/src/main/java/org/apache/wicket/event/IEvent.java @@ -30,6 +30,11 @@ public interface IEvent */ void stop(); + /** + * @return true iff event has been stopped. + */ + boolean isStop(); + /** * Stops the broadcast of this event any deeper into the hierarchy of the current sink */ diff --git a/wicket-core/src/main/java/org/apache/wicket/event/IEventAwareObject.java b/wicket-core/src/main/java/org/apache/wicket/event/IEventAwareObject.java new file mode 100644 index 00000000000..980d54ccf08 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/event/IEventAwareObject.java @@ -0,0 +1,23 @@ +/* + * 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.wicket.event; + +/** + * To mark an object (component, session, app) as containing annotated event handler methods. + */ +public interface IEventAwareObject { +} diff --git a/wicket-core/src/main/java/org/apache/wicket/event/OnEvent.java b/wicket-core/src/main/java/org/apache/wicket/event/OnEvent.java new file mode 100644 index 00000000000..901c289186d --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/event/OnEvent.java @@ -0,0 +1,42 @@ +/* + * 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.wicket.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to mark a public method (on a component) or on {@link org.apache.wicket.behavior.Behavior} + * as a handler of an Event. + *

+ * See {@link ReflectionEventDispatcher} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Repeatable(OnEvents.class) +public @interface OnEvent +{ + + /** + * @return The class of the payload + */ + Class value(); + +} diff --git a/wicket-core/src/main/java/org/apache/wicket/event/OnEvents.java b/wicket-core/src/main/java/org/apache/wicket/event/OnEvents.java new file mode 100644 index 00000000000..ede0f4c556b --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/event/OnEvents.java @@ -0,0 +1,37 @@ +/* + * 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.wicket.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * To allow adding more than one @{@link OnEvent} annotation + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnEvents +{ + + /** + * @return The class of the payload + */ + OnEvent[] value(); + +} diff --git a/wicket-core/src/main/java/org/apache/wicket/event/ReflectionEventDispatcher.java b/wicket-core/src/main/java/org/apache/wicket/event/ReflectionEventDispatcher.java new file mode 100644 index 00000000000..99efdd79090 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/event/ReflectionEventDispatcher.java @@ -0,0 +1,131 @@ +/* + * 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.wicket.event; + +import static java.util.Arrays.asList; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.apache.wicket.Component; +import org.apache.wicket.IEventDispatcher; +import org.apache.wicket.WicketRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IEventDispatcher that uses reflection in order to locate events methods. + */ +public class ReflectionEventDispatcher implements IEventDispatcher { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionEventDispatcher.class); + + /** + * If set to true then only sinks annotated with @{@link EventAwareObject} will be injected. + * Probably, this should be set to true in most applications and enforce the use of either @{@link EventAwareObject} + * annotation or the marker interface @{@link IEventAwareObject} + */ + private boolean restrictToEventAware = true; + + public ReflectionEventDispatcher() + { + } + + public ReflectionEventDispatcher(boolean restrictToEventAware) + { + this.restrictToEventAware = restrictToEventAware; + } + + @Override + public final void dispatchEvent(Object sink, IEvent event, Component component) + { + if (restrictToEventAware) + { + Class clazz = sink.getClass(); + if (!clazz.isAnnotationPresent(EventAwareObject.class) && !(sink instanceof IEventAwareObject)) + { + // object does not receives events + return; + } + } + executeEvent(sink, event); + } + + private void executeEvent(Object sink, IEvent event) + { + Class clazz = sink.getClass(); + for (Method method : getAllMethods(clazz)) + { + if (event.isStop()) + { + return; + } + if (isMethodAnEventMethod(method, event)) + { + method.setAccessible(true); + try + { + // we try to inject IEvent + method.invoke(sink, event); + } + catch (Exception e) + { + try + { + // we try to inject payload directly + method.invoke(sink, event.getPayload()); + } + catch (Exception e1) + { + LOGGER.error("Wrong signature of event method: sink {} and method {}", clazz, method); + throw new WicketRuntimeException("Wrong signature of event method: " + method.getName()); + } + } + } + } + } + + private Iterable getAllMethods(final Class clazz) + { + return new HashSet<>(getDeclared(clazz)); + } + + protected Set getDeclared(Class clazz) { + if (clazz == Object.class || clazz == null) + { + return Collections.emptySet(); + } + Set allMethods = new HashSet<>(); + allMethods.addAll(asList(clazz.getDeclaredMethods())); + allMethods.addAll(getDeclared(clazz.getSuperclass())); + return allMethods; + } + + protected boolean isMethodAnEventMethod(Method method, IEvent event) + { + Class payloadClass = event.getPayload().getClass(); + // we look for annotation + if (method.isAnnotationPresent(OnEvent.class)) + { + OnEvent onEvent = method.getAnnotation(OnEvent.class); + Class eventClass = onEvent.value(); + // and we check payload if of the right type + return eventClass.isAssignableFrom(payloadClass); + } + return false; + } +} diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/FrameworkSettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/FrameworkSettings.java index d193ca6a1ba..2f84181695c 100644 --- a/wicket-core/src/main/java/org/apache/wicket/settings/FrameworkSettings.java +++ b/wicket-core/src/main/java/org/apache/wicket/settings/FrameworkSettings.java @@ -45,8 +45,28 @@ */ public class FrameworkSettings implements IEventDispatcher { + /** + * Does the standard delivery of events. Override and do nothing if you want to disable it. + */ + private static class DefaultEventDispatcher implements IEventDispatcher + { + @Override + public void dispatchEvent(Object sink, IEvent event, Component component) + { + // direct delivery + if (component != null && sink instanceof IComponentAwareEventSink) + { + ((IComponentAwareEventSink)sink).onEvent(component, event); + } + else if (sink instanceof IEventSink) + { + ((IEventSink)sink).onEvent(event); + } + } + } private IDetachListener detachListener; + private IEventDispatcher defaultEventDispatcher = new DefaultEventDispatcher(); private List eventDispatchers = null; /** @@ -68,7 +88,7 @@ public FrameworkSettings(final Application application) * Gets the Wicket version. The Wicket version is in the same format as the version element in * the pom.xml file (project descriptor). The version is generated by maven in the build/release * cycle and put in the /META-INF/MANIFEST.MF file located in the root folder of the Wicket jar. - * + *

* The version usually follows one of the following formats: *
    *
  • major.minor[.bug] for stable versions. 1.1, 1.2, 1.2.1 are examples
  • @@ -112,7 +132,7 @@ public FrameworkSettings setDetachListener(IDetachListener detachListener) /** * Registers a new event dispatcher * - * @param dispatcher + * @param dispatcher {@link IEventDispatcher} * @return {@code this} object for chaining */ public FrameworkSettings add(IEventDispatcher dispatcher) @@ -129,26 +149,30 @@ public FrameworkSettings add(IEventDispatcher dispatcher) return this; } + /** + * @return Returns true if there is at least one event dispatcher + */ + public final boolean hasAnyAnyEventDispatchers() + { + if (defaultEventDispatcher != null) + { + return true; + } + return eventDispatchers != null && !eventDispatchers.isEmpty(); + } + /** * Dispatches event to registered dispatchers * * @see IEventDispatcher#dispatchEvent(Object, IEvent, Component) - * - * @param sink - * @param event - * @param component + * */ @Override public void dispatchEvent(Object sink, IEvent event, Component component) { - // direct delivery - if (component != null && sink instanceof IComponentAwareEventSink) + if (defaultEventDispatcher != null) { - ((IComponentAwareEventSink)sink).onEvent(component, event); - } - else if (sink instanceof IEventSink) - { - ((IEventSink)sink).onEvent(event); + defaultEventDispatcher.dispatchEvent(sink, event, component); } // additional dispatchers delivery @@ -162,6 +186,19 @@ else if (sink instanceof IEventSink) } } + /** + * Allows to set the default events dispatcher + * + * @param defaultEventDispatcher + * IEventDispatcher + * @return {@code this} object for chaining + */ + public FrameworkSettings setDefaultEventDispatcher(IEventDispatcher defaultEventDispatcher) + { + this.defaultEventDispatcher = defaultEventDispatcher; + return this; + } + /** * Sets the {@link ISerializer} that will be used to convert objects to/from byte arrays * diff --git a/wicket-core/src/test/java/org/apache/wicket/event/ReflectionEventDispatcherTest.java b/wicket-core/src/test/java/org/apache/wicket/event/ReflectionEventDispatcherTest.java new file mode 100644 index 00000000000..bbe446cfca7 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/event/ReflectionEventDispatcherTest.java @@ -0,0 +1,583 @@ +/* + * 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.wicket.event; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.apache.wicket.Component; +import org.apache.wicket.MockPageWithOneComponent; +import org.apache.wicket.behavior.Behavior; +import org.apache.wicket.markup.html.WebComponent; +import org.apache.wicket.util.tester.WicketTestCase; +import org.junit.jupiter.api.Test; + +class ReflectionEventDispatcherTest extends WicketTestCase +{ + + private static class TestPayload { + + } + + // we just disable event dispatching at application level + @Test + void testEventsDeliveryDisable() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(null); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(0, testComponent.invocationTimes); + assertEquals(0, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + + /** + * Testing ReflectionEventDispatcher event dispatchers in frameworksettings. This dispatcher + * invoke the methods annotated with @OnEvent + * */ + @Test + void dispatchToAnnotatedMethodNoRestrictions() + { + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 2, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + /** + * The same as the above but injecting IEvent. + */ + @Test + void dispatchToAnnotatedMethodNoRestrictionsReceivesIEvent() + { + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponentIEvent testComponent = new TestComponentIEvent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(2, testComponent.invocationTimes); + assertEquals(2, testComponent.getBehaviors(TestBehaviorIEvent.class).get(0).invocationTimes); + } + + /** + * Testing ReflectionEventDispatcher event dispatchers in frameworksettings. This dispatcher + * invoke the methods annotated with @OnEvent + * */ + @Test + void dispatchToAnnotatedMethodToAnnotatedObjects() + { + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(true)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + // as component and behavior are not annotated with @EventAwareObject only default event system will work + assertEquals(1, testComponent.invocationTimes); + assertEquals(1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void dispatchToAnnotatedMethodToAnnotatedObjectsAnonymous() + { + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + public void testCallbackEvent(IEvent event) + { + assertNotNull(event); + assertNotNull(event.getPayload()); + invocationTimes++; + } + + @Override + protected void addBehavior() { + add(new TestBehavior(){ + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + public void testCallbackEvent(IEvent event) + { + assertNotNull(event); + assertNotNull(event.getPayload()); + invocationTimes++; + } + }); + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + // as component and behavior are not annotated with @EventAwareObject only default event system will work + assertEquals(3, testComponent.invocationTimes); + assertEquals(3, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + /** + * The same as the above but injecting IEvent. + */ + @Test + void dispatchToAnnotatedMethodToAnnotatedEventAwareObject() + { + // both events will arrive + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponentIEvent testComponent = new TestComponentIEvent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(2, testComponent.invocationTimes); + assertEquals(2, testComponent.getBehaviors(TestBehaviorIEvent.class).get(0).invocationTimes); + } + + /** + * The same as the above but injecting IEvent. + */ + @Test + void dispatchToAnnotatedMethodInOnjectImplementingEventAwareObjectTestBehavior() + { + // both events will arrive + tester.getApplication().getFrameworkSettings().add(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + EventAwareObjectTestComponent testComponent = new EventAwareObjectTestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(2, testComponent.invocationTimes); + assertEquals(2, testComponent.getBehaviors(EventAwareObjectTestBehavior.class).get(0).invocationTimes); + } + + @Test + void replaceDefaultEvents() + { + // both events will arrive + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponentIEvent testComponent = new TestComponentIEvent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(1, testComponent.invocationTimes); + assertEquals(1, testComponent.getBehaviors(TestBehaviorIEvent.class).get(0).invocationTimes); + } + + @Test + void replaceDefaultEventsIEvent() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals( 1, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 2, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void replaceDefaultEventsAnnotatedNoDelivery() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(true)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + // no events will arrive as component is not annoated + assertEquals(0, testComponent.invocationTimes); + assertEquals( 0, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + + @Test + void testStop() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + private void testCallbackII(IEvent event) + { + // we stop event + event.stop(); + // thus behavior will not receive event + } + + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + private void testCallbackIII(TestPayload payload) + { + invocationTimes++; + } + + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals(1, testComponent.invocationTimes); + assertEquals( 0, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + + @Test + void testNoEventsReceivedBecauseOfOtherType() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadII()); + assertEquals( 0, testComponent.invocationTimes); + assertEquals( 0, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals( 1, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + + @Test + void testPayloadSubTypes() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID); + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIII()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 1, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesII() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + assertNotNull(payload); + invocationTimes++; + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIII()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesII_I() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(IEvent event) + { + assertNotNull(event); + invocationTimes++; + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIII()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesII_II() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(IEvent event) + { + assertNotNull(event); + invocationTimes++; + } + + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + assertNotNull(payload); + invocationTimes++; + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIII()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 3, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesIII() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayload()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 1, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesIV() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + private void testCallback(TestPayload payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + + @Override + protected void addBehavior() { + add(new TestBehavior() { + @OnEvent(TestPayloadIII.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + private void testCallbackX(TestPayload payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + + }); + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIV()); + // as TestPayloadIII is a subtype of TestPayload + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 2, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + @Test + void testPayloadSubTypesVI() + { + tester.getApplication().getFrameworkSettings().setDefaultEventDispatcher(new ReflectionEventDispatcher(false)); + MockPageWithOneComponent page = new MockPageWithOneComponent(); + TestComponent testComponent = new TestComponent(MockPageWithOneComponent.COMPONENT_ID) { + @OnEvent(TestPayloadIII.class) + @OnEvent(TestPayloadIV.class) + @SuppressWarnings("unused") + private void testCallback(TestPayloadIII payload) + { + // this should not be called + assertNotNull(payload); + invocationTimes++; + } + }; + page.add(testComponent); + page.send(page, Broadcast.DEPTH, new TestPayloadIII()); + assertEquals( 1, testComponent.invocationTimes); + assertEquals( 1, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + + page.send(page, Broadcast.DEPTH, new TestPayloadIV()); + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 2, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + + page.send(page, Broadcast.DEPTH, new TestPayloadII()); + assertEquals( 2, testComponent.invocationTimes); + assertEquals( 2, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + + page.send(page, Broadcast.DEPTH, new TestPayload()); + assertEquals( 3, testComponent.invocationTimes); + assertEquals( 3, testComponent.getBehaviors(TestBehavior.class).get(0).invocationTimes); + } + + private static class TestPayloadII { + + } + + private static class TestPayloadIII extends TestPayload { + + } + + private static class TestPayloadIV extends TestPayload { + + } + /** */ + public static class TestComponent extends WebComponent + { + private static final long serialVersionUID = 1L; + int invocationTimes = 0; + + TestComponent(String id) + { + super(id); + addBehavior(); + } + + protected void addBehavior() { + add(new TestBehavior()); + } + + /** */ + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + private void testCallback(TestPayload payload) + { + assertNotNull(payload); + invocationTimes++; + } + + @Override + public void onEvent(IEvent event) { + invocationTimes++; + } + } + + private static class TestBehavior extends Behavior + { + + private static final long serialVersionUID = 1; + + int invocationTimes = 0; + + @Override + public void onEvent(Component component, IEvent event) + { + invocationTimes++; + } + + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + protected void testCallback(TestPayload payload) + { + assertNotNull(payload); + invocationTimes++; + } + } + + public static class EventAwareObjectTestComponent extends TestComponent implements IEventAwareObject { + + EventAwareObjectTestComponent(String id) { + super(id); + } + + @Override + protected void addBehavior() { + add(new EventAwareObjectTestBehavior()); + } + } + + private static class EventAwareObjectTestBehavior extends TestBehavior implements IEventAwareObject{ + + } + + @EventAwareObject + public static class TestComponentIEvent extends WebComponent + { + private static final long serialVersionUID = 1L; + int invocationTimes = 0; + + TestComponentIEvent(String id) + { + super(id); + + add(new TestBehaviorIEvent()); + } + + @Override + public void onEvent(IEvent event) { + invocationTimes++; + } + + /** */ + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + public void testCallback(IEvent event) + { + assertNotNull(event); + assertNotNull(event.getPayload()); + invocationTimes++; + } + } + + @EventAwareObject + private static class TestBehaviorIEvent extends Behavior + { + + private static final long serialVersionUID = 1; + + int invocationTimes = 0; + + @Override + public void onEvent(Component component, IEvent event) + { + invocationTimes++; + } + + @OnEvent(TestPayload.class) + @SuppressWarnings("unused") + public void testCallback(IEvent event) + { + assertNotNull(event); + assertNotNull(event.getPayload()); + invocationTimes++; + } + } +}