/*******************************************************************************
* 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());
}
}