package simulator;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Term;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;
import simulator.Event.IResourceStateValueProvider;
import simulator.interfaces.INativeReceiver;
public class Simulator {
private DataTransferModel model;
private SystemState curState;
private List<INativeReceiver> systemReceivers = new ArrayList<>();
private Map<DataTransferChannel, Map<ResourceIdentifier, INativeReceiver>> nativeReceivers = new HashMap<>();
private Map<DataTransferChannel, INativeReceiver> nativeChannelReceivers = new HashMap<>();
public Simulator(DataTransferModel model) {
this.model = model;
init();
}
public DataTransferModel getModel() {
return model;
}
public SystemState init() {
curState = new SystemState();
for (ResourceHierarchy res: model.getResourceHierarchies()) {
if (res.getParent() == null) {
// root resource
Resource resource = new Resource(res);
curState.addResource(resource);
if (res.getInitialValue() != null) {
curState.updateResourceState(resource.getResourceIdentifier(), res.getInitialValue());
}
}
}
for (Channel channel: model.getChannels()) {
curState.addChannel((DataTransferChannel) channel);
}
return curState;
}
public SystemState getCurState() {
return curState;
}
/**
* Change the state of the system for a given input event.
*
* @param inputEvent an input event
* @return the next system state
* @throws ParameterizedIdentifierIsFutureWork
* @throws ResolvingMultipleDefinitionIsFutureWork
* @throws InvalidMessage
* @throws UnificationFailed
* @throws ValueUndefined
*/
public SystemState transition(Event inputEvent)
throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
SystemState nextSystemState = new SystemState(curState);
nextSystemState.addEvent(inputEvent);
fireEvent(inputEvent, curState, nextSystemState);
curState = nextSystemState;
for (INativeReceiver receiver: systemReceivers) {
receiver.onReceiveFromModel(inputEvent, nextSystemState);
}
return nextSystemState;
}
public void addSystemReceiver(INativeReceiver receiver) {
systemReceivers.add(receiver);
}
public void addNativeReceiver(INativeReceiver receiver, DataTransferChannel channel) {
nativeChannelReceivers.put(channel, receiver);
}
public void addNativeReceiver(INativeReceiver receiver, DataTransferChannel channel, Resource resource) {
Map<ResourceIdentifier, INativeReceiver> receivers = nativeReceivers.get(channel);
if (receivers == null) {
receivers = new HashMap<>();
nativeReceivers.put(channel, receivers);
}
receivers.put(resource.getResourceIdentifier(), receiver);
}
public void removeSystemReceiver(INativeReceiver receiver) {
systemReceivers.remove(receiver);
}
public void removeNativeReceiver(DataTransferChannel channel) {
nativeChannelReceivers.remove(channel);
}
public void removeNativeReceiver(DataTransferChannel channel, Resource resource) {
Map<ResourceIdentifier, INativeReceiver> receivers = nativeReceivers.get(channel);
if (receivers != null) {
receivers.remove(resource.getResourceIdentifier());
if (receivers.size() == 0) {
nativeReceivers.remove(channel);
}
}
}
/**
* Fire an given event and construct the next system state from the current system state.
*
* @param event an event
* @param curSystemState the current state of the system
* @param nextSystemState the next state of the system to be constructed
* @throws ParameterizedIdentifierIsFutureWork
* @throws ResolvingMultipleDefinitionIsFutureWork
* @throws InvalidMessage
* @throws UnificationFailed
* @throws ValueUndefined
*/
private void fireEvent(final Event event, final SystemState curSystemState, final SystemState nextSystemState)
throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
IResourceStateAccessor resouceStateAccessor = new IResourceStateAccessor() {
@Override
public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
ResourceIdentifier resId = event.getResourceIdentifier(target.getResource());
Resource res = curSystemState.getResource(resId);
if (res == null) return null;
return res.getState().getValue();
}
@Override
public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
ResourceIdentifier resId = event.getResourceIdentifier(target.getResource());
Resource res = nextSystemState.getResource(resId);
if (res == null) return null;
return res.getState().getValue();
}
@Override
public Expression getDirectStateAccessorFor(ResourcePath target, ResourcePath from) {
ResourceIdentifier resId = event.getResourceIdentifier(target);
Resource res = curSystemState.getResource(resId);
if (res == null) return null;
return res.getState().getValue();
}
};
DataTransferChannel channel = event.getChannel();
if (channel.getOutputResources().size() > 0) {
// For each output resource, calculate the next state.
for (ChannelMember out: channel.getOutputChannelMembers()) {
Expression nextResState = null;
if (!event.isInput()) {
nextResState = channel.deriveUpdateExpressionOf(out, resouceStateAccessor).getKey();
} else {
nextResState = channel.deriveUpdateExpressionOf(out, (Term) event.getMessage(), resouceStateAccessor);
}
ResourceIdentifier outResId = event.getOutputResourceIdentifier(out.getResource());
if (nextResState instanceof Term) {
nextResState = ((Term) nextResState).reduce();
}
ResourceIdentifier updatedOutResId = nextSystemState.updateResourceState(outResId, nextResState);
while (updatedOutResId != null) { // In addition to the target state, its descendants' states are also changed.
for (Event nextEvent: getNextEvents(updatedOutResId, curSystemState, nextSystemState)) {
fireEvent(nextEvent, curSystemState, nextSystemState);
}
updatedOutResId = (ResourceIdentifier) updatedOutResId.getParent();
}
}
} else if (channel.isNative()) {
// A native output event channel
INativeReceiver receiver = nativeChannelReceivers.get(channel); // receiver for the channel
if (receiver != null) receiver.onReceiveFromModel(event, nextSystemState);
if (nativeReceivers.get(channel) != null) {
receiver = nativeReceivers.get(channel).get(event.getInputResource().getResourceIdentifier()); // receiver for the channel and resource
if (receiver != null) receiver.onReceiveFromModel(event, nextSystemState);
}
}
}
private Set<Event> getNextEvents(ResourceIdentifier inResId, final SystemState curSystemState, final SystemState nextSystemState)
throws ParameterizedIdentifierIsFutureWork, ResolvingMultipleDefinitionIsFutureWork, InvalidMessage, UnificationFailed, ValueUndefined {
Set<Event> nextEvents = new HashSet<>();
IResourceStateValueProvider resourceStateValueProvider = new IResourceStateValueProvider() {
@Override
public Expression getCurrentStateValueOf(ResourceIdentifier resId) {
if (curSystemState.getResource(resId) == null) return null;
return curSystemState.getResource(resId).getState().getValue();
}
@Override
public Expression getNextStateValueOf(ResourceIdentifier resId) {
if (nextSystemState.getResource(resId).getState() == null) return null;
return nextSystemState.getResource(resId).getState().getValue();
}
};
for (Map.Entry<DataTransferChannel, ChannelState> chEntry: nextSystemState.getChannelStates().entrySet()) {
DataTransferChannel channel = chEntry.getKey();
ChannelState nextChState = chEntry.getValue();
Map<ChannelMember, Set<ChannelMember>> dependency = channel.getMemberDependency();
Map<ChannelMember, Set<ChannelMember>> invDependency = new HashMap<>();
for (ChannelMember dependingMem: dependency.keySet()) {
for (ChannelMember dependedMem: dependency.get(dependingMem)) {
Set<ChannelMember> dependings = invDependency.get(dependedMem);
if (dependings == null) {
dependings = new HashSet<>();
invDependency.put(dependedMem, dependings);
}
dependings.add(dependingMem);
}
}
for (ResourcePath inResPath: channel.getInputResources()) {
if (inResId.isInstanceOf(inResPath)) {
// Update the channel state and resource identifiers by the update of the input resource.
for (ChannelMember dependedMem: invDependency.keySet()) {
if (inResPath == dependedMem.getResource()) {
// If some depending resources are to be updated by the update of an depended input resource.
Event nextEvent = new Event(channel, inResPath, nextSystemState.getResource(inResId));
nextEvent.updateDependingParameters(resourceStateValueProvider);
if (nextChState == null) {
nextChState = new ChannelState(channel);
nextSystemState.updateChannelState(channel, nextChState);
}
List<Constant> channelSelValues = nextEvent.getChannelSelectorValues();
for (Map.Entry<Expression, Expression> paramEnt: nextEvent.getDependingParameters().entrySet()) {
nextChState.addDependingParamAndValue(channelSelValues, paramEnt.getKey(), paramEnt.getValue());
}
nextEvents.add(nextEvent);
}
}
if (invDependency.size() == 0) {
Event nextEvent = new Event(channel, inResPath, nextSystemState.getResource(inResId));
nextEvent.setMessage(nextEvent.updateDependingParameters(resourceStateValueProvider));
nextEvents.add(nextEvent);
}
if (nextChState != null) {
for (ChannelMember dependingMem: dependency.keySet()) {
if (inResPath == dependingMem.getResource()) {
// If a depending resource is directly updated.
ResourcePath filledResPath = inResId;
ResourcePath unfilledResPath = inResPath;
Map<Expression, Expression> selectorVarToVal = new HashMap<>();
Map<Expression, Expression> dependingVarToVal = new HashMap<>();
for (int i = 0; i < unfilledResPath.getPathParams().size(); i++) {
Expression var = unfilledResPath.getPathParams().get(i);
Expression val = filledResPath.getPathParams().get(i);
boolean isSelector = false;
for (Selector sel: channel.getAllSelectors()) {
if (sel.getExpression().equals(var)) {
isSelector = true;
break;
}
}
if (isSelector) {
selectorVarToVal.put(var, val);
} else {
dependingVarToVal.put(var, val);
}
}
for (List<Constant> channelSelectorValues: nextChState.getDependedChannelSelectorValues(dependingVarToVal)) {
// Guess every tuple of channel selector values that may affects the updated resource.
boolean doesMatch = true;
for (Expression var: selectorVarToVal.keySet()) {
for (int i = 0; i < channel.getAllSelectors().size(); i++) {
if (channel.getAllSelectors().get(i).getExpression().equals(var)) {
if (!channelSelectorValues.get(i).equals(selectorVarToVal.get(var))) {
// If the value of a selector in the updated resource path does not matches a guessed channel selector value.
doesMatch = false;
}
}
}
}
if (doesMatch) {
Event nextEvent = new Event(channel, inResPath, nextSystemState.getResource(inResId), channelSelectorValues, dependingVarToVal);
nextEvents.add(nextEvent);
}
}
}
}
}
}
}
}
return nextEvents;
}
}