/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed 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 com.analog.lyric.dimple.test.events; import static com.analog.lyric.util.test.ExceptionTester.*; import static org.junit.Assert.*; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; import org.junit.Test; import com.analog.lyric.collect.ReleasableIterator; import com.analog.lyric.dimple.environment.DimpleEnvironment; import com.analog.lyric.dimple.events.DimpleEvent; import com.analog.lyric.dimple.events.DimpleEventBlocker; import com.analog.lyric.dimple.events.DimpleEventHandler; import com.analog.lyric.dimple.events.DimpleEventListener; import com.analog.lyric.dimple.events.DimpleEventListener.IHandlerEntry; import com.analog.lyric.dimple.events.DimpleEventListener.IHandlersForSource; import com.analog.lyric.dimple.events.IDimpleEventHandler; import com.analog.lyric.dimple.events.IDimpleEventSource; import com.analog.lyric.dimple.events.IModelEventSource; import com.analog.lyric.dimple.events.ISolverEventSource; import com.analog.lyric.dimple.events.ModelEvent; import com.analog.lyric.dimple.events.SolverEvent; import com.analog.lyric.dimple.factorfunctions.Normal; import com.analog.lyric.dimple.model.core.FactorGraph; import com.analog.lyric.dimple.model.factors.Factor; import com.analog.lyric.dimple.model.variables.Real; import com.analog.lyric.dimple.model.variables.Variable; import com.analog.lyric.dimple.solvers.gibbs.GibbsSolver; import com.analog.lyric.dimple.solvers.gibbs.GibbsSolverGraph; import com.analog.lyric.dimple.solvers.gibbs.ISolverVariableGibbs; import com.analog.lyric.dimple.solvers.interfaces.ISolverFactor; import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph; import com.analog.lyric.dimple.test.DimpleTestBase; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; /** * * @since 0.06 * @author Christopher Barber */ public class TestDimpleEventListener extends DimpleTestBase { static class TestModelEvent extends ModelEvent { private static final long serialVersionUID = 1L; TestModelEvent(IModelEventSource source) { super(source); } @Override protected void printDetails(PrintStream out, int verbosity) { } } static class TestVariableEvent extends TestModelEvent { private static final long serialVersionUID = 1L; TestVariableEvent(Variable source) { super(source); } } static class TestSolverEvent extends SolverEvent { private static final long serialVersionUID = 1L; TestSolverEvent(ISolverEventSource source) { super(source); } @Override public @Nullable IModelEventSource getModelObject() { return getSource().getModelEventSource(); } @Override protected void printDetails(PrintStream out, int verbosity) { } } static class TestSolverFactorEvent extends TestSolverEvent { private static final long serialVersionUID = 1L; TestSolverFactorEvent(ISolverFactor source) { super(source); } } static class TestEventHandler<Event extends DimpleEvent> extends DimpleEventHandler<Event> { final boolean _consume; final static List<TestEventHandler<?>> handledBy = new ArrayList<TestEventHandler<?>>(); static <T extends DimpleEvent> TestEventHandler<T> create(boolean consume) { return new TestEventHandler<T>(consume); } TestEventHandler(boolean consume) { _consume = consume; } @Override public void handleEvent(Event event) { handledBy.add(this); if (_consume) { event.consumed(true); } } } @SuppressWarnings("null") @Test public void testListener() { DimpleEnvironment env = DimpleEnvironment.active(); // Set up model/solver for test FactorGraph model = new FactorGraph(); model.setName("model"); assertSame(env, model.getEnvironment()); Variable v1 = new Real(); model.addVariables(v1); v1.setName("v1"); FactorGraph template = new FactorGraph(); Variable b1 = new Real(); template.addBoundaryVariables(b1); template.addFactor(new Normal(0.0, 1.0), b1).setName("normal"); FactorGraph subgraph = model.addFactor(template, v1); Factor f1 = subgraph.getFactorByName("normal"); assertNotNull(f1); GibbsSolverGraph sgraph = model.setSolverFactory(new GibbsSolver()); ISolverVariableGibbs sv1 = sgraph.getSolverVariable(v1); assertNotNull(sv1); ISolverFactor sf1 = sgraph.getSolverFactor(f1); assertSame(f1, sf1.getModelEventSource()); assertSame(f1, sf1.getModelObject()); ISolverFactorGraph ssubgraph = sf1.getParentGraph(); assertNotSame(ssubgraph, sgraph); assertSame(sgraph, ssubgraph.getParentGraph()); // Some events TestModelEvent modelEvent = new TestModelEvent(model); assertSame(model, modelEvent.getSource()); assertSame(model, modelEvent.getModelObject()); assertFalse(modelEvent.consumed()); // Test empty listener DimpleEventListener listener = new DimpleEventListener(); assertInvariants(listener); assertTrue(Iterables.isEmpty(listener.allHandlerPerSource())); assertFalse(listener.isListeningFor(TestModelEvent.class, model)); assertFalse(listener.isListeningFor(ModelEvent.class, model)); assertFalse(DimpleEventListener.sourceHasListenerFor(model, ModelEvent.class)); assertTrue(listener.getHandlersFor(TestModelEvent.class, model).isEmpty()); assertHandledBy(listener, modelEvent); assertFalse(modelEvent.consumed()); expectThrow(NullPointerException.class, listener, "register", null, DimpleEvent.class, true, model); expectThrow(NullPointerException.class, listener, "register", null, DimpleEvent.class, true, model); expectThrow(NullPointerException.class, listener, "register", null, DimpleEvent.class, true, model); // Test eventSources() order ReleasableIterator<IDimpleEventSource> sources1 = listener.eventSources(sv1); ReleasableIterator<IDimpleEventSource> sources2 = listener.eventSources(sv1); assertNotSame(sources1, sources2); assertEventSources(sources1, sv1, v1, sgraph, model, env); assertEventSources(sources2, sv1, v1, sgraph, model, env); expectThrow(UnsupportedOperationException.class, null, sources1, "remove"); sources1.release(); sources2.release(); sources1 = listener.eventSources(v1); assertSame(sources1, sources2); assertEventSources(sources1, v1, model, env); sources1.release(); sources1 = listener.eventSources(sf1); assertEventSources(sources1, sf1, f1, ssubgraph, subgraph, sgraph, model, env); sources1.release(); sources1 = listener.eventSources(f1); assertEventSources(sources1, f1, subgraph, model, env); sources1.release(); // Single handler TestEventHandler<TestModelEvent> handler1 = TestEventHandler.create(false); expectThrow(NullPointerException.class, listener, "register", null, DimpleEvent.class, true, model); expectThrow(NullPointerException.class, listener, "register", handler1, null, true, model); expectThrow(NullPointerException.class, listener, "register", handler1, DimpleEvent.class, true, null); assertTrue(listener.isEmpty()); listener.register(handler1, TestModelEvent.class, false, model); listener.register(handler1, TestModelEvent.class, false, model); // no effect assertInvariants(listener); assertTrue(listener.isListeningFor(TestModelEvent.class, model)); List<IHandlerEntry> entries = listener.getHandlersFor(TestModelEvent.class, model); assertEquals(1, entries.size()); IHandlerEntry entry = entries.get(0); assertSame(model, entry.eventSource()); assertNotEquals(entry, "foo"); assertEquals(TestModelEvent.class, entry.eventClass()); assertFalse(entry.handleSubclasses()); assertSame(handler1, entry.eventHandler()); assertHandledBy(listener, modelEvent, handler1); expectThrow(UnsupportedOperationException.class, null, listener.allHandlerPerSource().iterator(), "remove"); // sourceHasListenerFor assertFalse(DimpleEventListener.sourceHasListenerFor(model, TestModelEvent.class)); model.getEnvironment().setEventListener(listener); assertTrue(DimpleEventListener.sourceHasListenerFor(model, TestModelEvent.class)); assertFalse(DimpleEventListener.sourceHasListenerFor(model, TestSolverEvent.class)); // Add a catch-all handler TestEventHandler<DimpleEvent> handleAll = TestEventHandler.create(false); listener.register(handleAll, DimpleEvent.class, true, model); assertInvariants(listener); assertHandledBy(listener, new TestModelEvent(model), handler1, handleAll); assertHandledBy(listener, new TestModelEvent(f1), handler1, handleAll); assertHandledBy(listener, new TestVariableEvent(v1), handleAll); assertHandledBy(listener, new TestSolverEvent(sf1), handleAll); // Block events from percolating past the solver subgraph listener.block(DimpleEvent.class, true, ssubgraph); assertInvariants(listener); assertHandledBy(listener, new TestSolverEvent(sf1)); TestEventHandler<TestSolverFactorEvent> handleSolverFactorEvent = TestEventHandler.create(false); listener.register(handleSolverFactorEvent, TestSolverFactorEvent.class, false, sf1); assertInvariants(listener); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleSolverFactorEvent); assertHandledBy(listener, new TestSolverEvent(sf1)); assertEquals(3, Iterables.size(listener.allHandlerPerSource())); assertFalse(listener.unblock(DimpleEvent.class, sf1)); assertFalse(listener.unblock(TestModelEvent.class, ssubgraph)); assertTrue(listener.unblock(DimpleEvent.class, ssubgraph)); assertInvariants(listener); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleSolverFactorEvent, handleAll); assertHandledBy(listener, new TestSolverEvent(sf1), handleAll); assertFalse(listener.unregisterSource(v1)); assertTrue(listener.unregisterSource(model)); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleSolverFactorEvent); listener.register(handleAll, DimpleEvent.class, true, model); listener.register(handleAll, SolverEvent.class, true, ssubgraph); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleSolverFactorEvent, handleAll, handleAll); listener.unregisterAll(); assertInvariants(listener); assertTrue(Iterables.isEmpty(listener.allHandlerPerSource())); assertFalse(listener.unregister(handleAll, DimpleEvent.class, model)); listener.register(handleAll, TestSolverEvent.class, true, model); listener.register(handleAll, TestSolverFactorEvent.class, true, model); listener.register(handleAll, TestVariableEvent.class, true, model); listener.register(handleAll, TestModelEvent.class, true, model); // These will override the previous entries since they differ only in their subclass setting: listener.register(handleAll, TestVariableEvent.class, false, model); listener.register(handleAll, TestModelEvent.class, false, model); listener.register(handleAll, TestSolverEvent.class, false, model); listener.register(handleAll, TestSolverFactorEvent.class, false, model); assertInvariants(listener); for (IHandlersForSource allHandlers : listener.allHandlerPerSource()) { for (IHandlerEntry e : allHandlers.handlerEntries()) { assertFalse(e.handleSubclasses()); } } assertHandledBy(listener, new TestSolverEvent(sf1), handleAll); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleAll); assertHandledBy(listener, new TestModelEvent(v1), handleAll); assertHandledBy(listener, new TestVariableEvent(v1), handleAll); assertTrue(listener.unregister(handleAll, TestSolverEvent.class, model)); assertHandledBy(listener, new TestSolverEvent(sf1)); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleAll); listener.register(handleSolverFactorEvent, TestSolverFactorEvent.class, false, model); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleAll, handleSolverFactorEvent); listener.unregister(handleSolverFactorEvent, TestSolverFactorEvent.class, model); assertHandledBy(listener, new TestSolverFactorEvent(sf1), handleAll); listener.unregisterAll(); assertTrue(listener.isEmpty()); DimpleEventHandler<DimpleEvent> anotherHandler = new TestEventHandler<DimpleEvent>(false); listener.register(handleAll, DimpleEvent.class, true, model); listener.register(anotherHandler, DimpleEvent.class, true, model); listener.register(handleAll, DimpleEvent.class, true, sf1); listener.register(anotherHandler, DimpleEvent.class, true, sf1); listener.unregisterAll(anotherHandler); for (IHandlersForSource allHandlers : listener.allHandlerPerSource()) { for (IHandlerEntry e : allHandlers.handlerEntries()) { assertNotSame(anotherHandler, e.eventHandler()); } } } @Test @SuppressWarnings("deprecation") public void testDefaultListener() { // // Test defaultListener // DimpleEventListener listener = new DimpleEventListener(); DimpleEventListener defaultListener = DimpleEventListener.getDefault(); assertTrue(defaultListener.isDefault()); assertTrue(defaultListener.isEmpty()); assertInvariants(defaultListener); assertSame(defaultListener, DimpleEventListener.getDefault()); assertSame(defaultListener, DimpleEventListener.setDefault(listener)); assertSame(listener, DimpleEventListener.setDefault(null)); assertNull(DimpleEventListener.setDefault(null)); assertFalse(defaultListener.isDefault()); DimpleEventListener defaultListener2 = DimpleEventListener.getDefault(); assertTrue(defaultListener2.isDefault()); assertInvariants(defaultListener2); assertNotSame(defaultListener, defaultListener2); } private void assertEventSources(Iterator<IDimpleEventSource> iter, IDimpleEventSource ... expected) { List<IDimpleEventSource> actual = new ArrayList<IDimpleEventSource>(); Iterators.addAll(actual, iter); assertFalse(iter.hasNext()); assertNull(iter.next()); assertArrayEquals(actual.toArray(), expected); } private void assertHandledBy(DimpleEventListener listener, DimpleEvent event, TestEventHandler<?> ... handlers) { event.consumed(false); TestEventHandler.handledBy.clear(); listener.raiseEvent(event); assertArrayEquals(handlers, TestEventHandler.handledBy.toArray()); final int nHandlers = handlers.length; final boolean expectedListening = !(nHandlers == 0 || nHandlers == 1 && handlers[nHandlers - 1].isBlocker()); final IDimpleEventSource source = Objects.requireNonNull(event.getSource()); assertEquals(expectedListening, listener.isListeningFor(event.getClass(), source)); } private void assertInvariants(DimpleEventListener listener) { boolean hasHandlers = false; for (IHandlersForSource handlers : listener.allHandlerPerSource()) { hasHandlers = true; IDimpleEventSource source = handlers.eventSource(); assertNotNull(source); List<IHandlerEntry> entries = handlers.handlerEntries(); assertFalse(entries.isEmpty()); for (int i = entries.size(); --i >= 0; ) { final IHandlerEntry entry = entries.get(i); final Class<? extends DimpleEvent> eventClass = entry.eventClass(); final IDimpleEventHandler<?> handler = entry.eventHandler(); assertSame(source, entry.eventSource()); assertTrue(DimpleEvent.class.isAssignableFrom(eventClass)); assertEquals(handler.isBlocker(), handler == DimpleEventBlocker.INSTANCE); // subclasses must come before superclasses in the list. for (int j = i; --j >= 0; ) { final IHandlerEntry prevEntry = entries.get(j); assertNotEquals(prevEntry, entry); assertNotEquals(prevEntry.hashCode(), entry.hashCode()); final Class<? extends DimpleEvent> prevEventClass = prevEntry.eventClass(); assertFalse(prevEventClass.isAssignableFrom(eventClass)); } List<IHandlerEntry> entries2 = listener.getHandlersFor(eventClass, source); assertFalse(entries2.isEmpty()); // Either the entry is found in the list of handlers for the class and source or the // handler list ends in a blocker. assertTrue(entries2.indexOf(entry) >= 0 || entries2.get(entries2.size() -1).eventHandler().isBlocker()); } } assertEquals(!hasHandlers, listener.isEmpty()); } }