/*******************************************************************************
* Copyright 2013 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.solvers.core;
import java.util.Objects;
import org.eclipse.jdt.annotation.Nullable;
import com.analog.lyric.dimple.events.IDimpleEventListener;
import com.analog.lyric.dimple.events.SolverEvent;
import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.model.core.Node;
import com.analog.lyric.dimple.solvers.core.parameterizedMessages.IParameterizedMessage;
import com.analog.lyric.dimple.solvers.interfaces.ISolverEdgeState;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph;
import com.analog.lyric.dimple.solvers.interfaces.ISolverNode;
/**
* Abstract base implementation of {@link ISolverNode}
* @param <MNode> is the corresponding model {@link Node} type.
*/
public abstract class SNode<MNode extends Node> extends SChild<MNode> implements ISolverNode
{
/*-----------
* Constants
*/
/**
* Bits in {@link #_flags} reserved by this class and its superclasses.
*/
@SuppressWarnings("hiding")
protected static final int RESERVED_FLAGS = 0xFF000000;
private static final int MESSAGE_EVENT_MASK = 0x03000000;
private static final int MESSAGE_EVENT_UNKNOWN = 0x00000000;
private static final int MESSAGE_EVENT_NONE = 0x01000000;
private static final int MESSAGE_EVENT_ENABLED = 0x03000000;
protected static final int EVENT_MASK = MESSAGE_EVENT_MASK;
/*--------------
* Construction
*/
public SNode(MNode n)
{
super(n);
}
@Override
public String toString()
{
return String.format("[%s %s]", getClass().getSimpleName(), _model.getQualifiedName());
}
/*---------------------
* ISolverNode methods
*/
@Override
public ISolverNode getSibling(int edge)
{
final Node sibling = getModelObject().getSibling(edge);
return getSolverMapping().getSolverNode(sibling);
}
@Override
public int getSiblingCount()
{
return _model.getSiblingCount();
}
/**
* Remove solver edge state for specified sibling of this node.
*
* @param edge is a non-negative index less than {@link #getSiblingCount()}.
* @since 0.08
*/
public void removeSiblingEdgeState(int edge)
{
Node node = _model;
if (edge < node.getSiblingCount())
{
requireParentGraph().removeSolverEdge(node.getSiblingEdgeState(edge));
}
}
/**
* Remove solver edge state for all siblings of this node.
*
* @since 0.08
*/
public void removeSiblingEdgeState()
{
final Node node = _model;
final ISolverFactorGraph parent = requireParentGraph();
for (int edge = 0, n = node.getSiblingCount(); edge < n; ++edge)
{
parent.removeSolverEdge(node.getSiblingEdgeState(edge));
}
}
@Deprecated
@Override
public void setInputMsg(int portIndex, Object obj)
{
setInputMsgValues(portIndex, obj);
}
@Deprecated
@Override
public void setOutputMsg(int portIndex, Object obj)
{
setOutputMsgValues(portIndex, obj);
}
@Deprecated
@Override
public void setInputMsgValues(int portIndex, Object obj)
{
throw new DimpleException("Not supported by " + this);
}
@Deprecated
@Override
public void setOutputMsgValues(int portIndex, Object obj)
{
throw new DimpleException("Not supported by " + this);
}
@Override
public void update()
{
if (raiseMessageEvents())
{
final IParameterizedMessage[] oldMessages = cloneMessages();
doUpdate();
final IParameterizedMessage[] newMessages = cloneMessages();
if (newMessages != null)
{
for (int edge = 0, nEdges = newMessages.length; edge < nEdges; ++edge)
{
final IParameterizedMessage oldMessage = oldMessages != null ? oldMessages[edge] : null;
final IParameterizedMessage newMessage = newMessages[edge];
if (newMessage != null)
{
raiseEvent(createMessageEvent(edge, oldMessage, newMessage));
}
}
}
}
else
{
doUpdate();
}
}
@Override
public void updateEdge(int edge)
{
if (raiseMessageEvents())
{
final IParameterizedMessage oldMessage = cloneMessage(edge);
doUpdateEdge(edge);
final IParameterizedMessage newMessage = cloneMessage(edge);
if (newMessage != null)
{
raiseEvent(createMessageEvent(edge, oldMessage, newMessage));
}
}
else
{
doUpdateEdge(edge);
}
}
/*---------------------------
* SolverEventSource methods
*/
@Override
protected int getEventMask()
{
return EVENT_MASK;
}
/*-------------------------
* Protected SNode methods
*/
/**
* Returns a clone of outgoing message for given {@code edge}.
* <p>
* @param edge index in the range [0, {@link #getSiblingCount()}-1].
* @return clone of message if applicable. May return null if there is no message or if subclass
* does not support messages. In the latter case, {@link #supportsMessageEvents()} should return false.
* <p>
* The default implementation returns null.
* <p>
* Subclasses that override this method to return a non-null message should also override
* {@link #supportsMessageEvents()}.
* <p>
* @since 0.06
*/
protected @Nullable IParameterizedMessage cloneMessage(int edge)
{
return null;
}
private final @Nullable IParameterizedMessage[] cloneMessages()
{
IParameterizedMessage[] messages = null;
final int size = getSiblingCount();
if (size > 0)
{
final IParameterizedMessage firstMessage = cloneMessage(0);
if (firstMessage != null)
{
messages = new IParameterizedMessage[size];
messages[0] = firstMessage;
for (int i = 1; i < size; ++i)
{
messages[i] = cloneMessage(i);
}
}
}
return messages;
}
protected void doUpdate()
{
for (int i = 0, end = getSiblingCount(); i < end; i++)
{
doUpdateEdge(i);
}
}
protected abstract void doUpdateEdge(int edge);
/**
* Creates a {@link IMessageUpdateEvent} event.
* <p>
* Default implementation returns null. When this method returns null, then {@link #supportsMessageEvents()}
* should also return false.
* <p>
* @param edge is the outgoing edge for the messages.
* @param oldMessage is the previous value of the message, which may be null.
* @param newMessage is the new message value, which must not be null.
* @since 0.06
*/
protected @Nullable SolverEvent createMessageEvent(
int edge,
@Nullable IParameterizedMessage oldMessage,
IParameterizedMessage newMessage)
{
return null;
}
@Override
public @Nullable ISolverEdgeState getSiblingEdgeState(int siblingIndex)
{
return requireParentGraph().getSolverEdge(_model.getSiblingEdgeIndex(siblingIndex));
}
/**
* If {@link #supportsMessageEvents()}, this returns the base type for all message
* events that can be created by this object. Default implementation returns null.
* @since 0.06
*/
protected @Nullable Class<? extends SolverEvent> messageEventType()
{
return null;
}
/**
* Indicate subclass has a concept of passing a {@link IParameterizedMessage} messages.
* <p>
* Should only return true if:
* <ul>
* <li>{@link #cloneMessage(int)} can return non-null message
* <li>{@link #messageEventType()} is non-null
* <li>{@link #createMessageEvent} can return a non-null event
* </ul>
* Since {@link SVariableBase} and {@link SFactorBase} implement the latter two methods, subclasses
* of those classes that support message events only need to implement {@link #cloneMessage(int)} and
* this method.
* <p>
* The default implementation returns false.
* <p>
* @since 0.06
*/
protected boolean supportsMessageEvents()
{
return false;
}
/*-----------------
* Private methods
*/
/**
* Indicates whether to generate {@link IMessageUpdateEvent}s.
* @since 0.06
*/
private boolean raiseMessageEvents()
{
final int flags = _flags & MESSAGE_EVENT_MASK;
if (flags == MESSAGE_EVENT_NONE)
{
// Check this first to minimize overhead when known to be disabled
return false;
}
else if (flags == MESSAGE_EVENT_UNKNOWN)
{
boolean enabled = false;
if (supportsMessageEvents())
{
final IDimpleEventListener listener = getEventListener();
if (listener != null)
{
enabled = listener.isListeningFor(Objects.requireNonNull(messageEventType()), this);
}
}
setFlagValue(MESSAGE_EVENT_MASK, enabled ? MESSAGE_EVENT_ENABLED : MESSAGE_EVENT_NONE);
return enabled;
}
else
{
return flags == MESSAGE_EVENT_ENABLED;
}
}
}