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 curState.addResource(new Resource(res)); } } 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; } }