/******************************************************************************* * Copyright (c) 2010-2013, Embraer S.A., Budapest University of Technology and Economics * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Marton Bur, Abel Hegedus, Akos Horvath - initial API and implementation *******************************************************************************/ /** * */ package hu.bme.mit.massif.simulink.api.util.bus; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import java.util.ArrayList; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; import com.google.common.collect.Maps; import hu.bme.mit.massif.models.simulink.util.NextOutPortInPathMatch; import hu.bme.mit.massif.simulink.Block; import hu.bme.mit.massif.simulink.BusCreator; import hu.bme.mit.massif.simulink.BusSelector; import hu.bme.mit.massif.simulink.BusSignalMapping; import hu.bme.mit.massif.simulink.BusSpecification; import hu.bme.mit.massif.simulink.InPort; import hu.bme.mit.massif.simulink.OutPort; import hu.bme.mit.massif.simulink.api.util.DepthFirstSearch; import hu.bme.mit.massif.simulink.api.util.PathMatcherGraphDataSource; /** * Utility class for computing the mapping path for output signals in a Bus Selector. Given a bus signal mapping, the * {@link #findMappingPath(BusSignalMapping)} method returns the string path if the source and target outports are * correct. * * The class is initialized with a resource set and will work on mappings that are contained by the resource set. * * @author Abel Hegedus * */ public class BusSignalMappingPathFinder { BusSignalMapper mapper; /** * Creates a path finder for mappings in models. * * @param mapper */ public BusSignalMappingPathFinder(BusSignalMapper mapper) { this.mapper = mapper; } /** * Finds the path string for the given mapping. It first checks that the mapping is correctly set (see * {@link #checkMappingArgument(BusSignalMapping, IncQueryEngine)}), then uses depth first search to find signal * paths between the from and to outports. Finally, the signal path is traversed to collect the line names that make * up the path string. * * @param mapping * the bus signal mapping that identifies the outport that is selected. * @return the path string that selects the given signal from the bus * @throws IllegalArgumentException * if the provided mapping is not correctly set up * @throws IllegalStateException * if the path computation finds an inconsistency between the model and the mapping */ public String findMappingPath(BusSignalMapping mapping) { checkMappingArgument(mapping, mapper.getEngine()); OutPort mappingFrom = mapping.getMappingFrom(); OutPort mappingTo = mapping.getMappingTo(); mapper.logDebug("[PathFinder] Computing bus signal mapping path from %s to %s", mapper.getFQNOrEmpty(mappingFrom), mapper.getFQNOrEmpty(mappingTo)); List<Deque<Entry<OutPort, InPort>>> paths = findPaths(mappingFrom, mappingTo); printPaths(paths); List<String> results = new ArrayList<String>(); for (Deque<Entry<OutPort, InPort>> path : paths) { path.removeLast(); // last selector does not alter path String mappingStringFromPath = findMappingStringFromPath(mappingTo, path); results.add(mappingStringFromPath); } checkState(!results.isEmpty(), "Could not find mapping path based on outport lists!"); checkState(results.size() == 1, "Multiple paths found: " + results.toString()); return results.get(0); } private void checkMappingArgument(BusSignalMapping mapping, ViatraQueryEngine engine) { checkArgument(mapping != null, "Mapping cannot be null!"); checkArgument(mapping.eResource().getResourceSet() == engine.getScope(), "Mapping is not part of correct resource set!"); checkArgument(mapping.getSelector() != null, "Selector cannot be null in mapping!"); checkArgument(mapping.getMappingFrom() != null, "From port cannot be null in mapping!"); checkArgument(mapping.getMappingTo() != null, "To port cannot be null in mapping!"); checkArgument(mapping.getMappingTo().getContainer() == mapping.getSelector()); } private List<Deque<Entry<OutPort, InPort>>> findPaths(OutPort mappingFrom, OutPort mappingTo) { List<Deque<Object>> paths = searchForUnfilteredPaths(mappingFrom, mappingTo); checkState(!paths.isEmpty(), "Cannot find path between ports " + mapper.getFQNOrEmpty(mappingFrom) + " and " + mapper.getFQNOrEmpty(mappingTo)); if (mapper.isDebugging()) { StringBuilder sb = new StringBuilder(); for (Deque<Object> linkedList : paths) { sb.append("Found unfiltered path:\n"); for (Object object : linkedList) { sb.append(mapper.getFQNOrEmpty((OutPort) object) + "\n"); } } mapper.logDebug(sb.toString()); } List<Deque<Entry<OutPort, InPort>>> filteredPaths = new ArrayList<Deque<Entry<OutPort, InPort>>>(); for (Deque<Object> path : paths) { Deque<Entry<OutPort, InPort>> inPortList = filterPath(mappingFrom, mappingTo, path); filteredPaths.add(inPortList); } return filteredPaths; } private List<Deque<Object>> searchForUnfilteredPaths(OutPort mappingFrom, OutPort mappingTo) { PathMatcherGraphDataSource<NextOutPortInPathMatch> matcherGraphDataSource = new PathMatcherGraphDataSource<NextOutPortInPathMatch>( mapper.getNextOutPortInPathMatcher()); matcherGraphDataSource.setTarget(mappingTo); DepthFirstSearch search = new DepthFirstSearch(); List<Deque<Object>> paths = search.depthFirstSearch(matcherGraphDataSource, mappingFrom, mappingTo); return paths; } private Deque<Entry<OutPort, InPort>> filterPath(OutPort mappingFrom, OutPort mappingTo, Deque<Object> path) { Iterator<Object> unfilteredPathIterator = path.iterator(); if (unfilteredPathIterator.hasNext()) { Object firstOutPort = unfilteredPathIterator.next(); checkState(firstOutPort.equals(mappingFrom), "Path does not start from port " + mapper.getFQNOrEmpty(mappingFrom)); } Deque<Entry<OutPort, InPort>> inPortList = new LinkedList<Entry<OutPort, InPort>>(); filterPathTail(mappingFrom, unfilteredPathIterator, inPortList); checkState(!inPortList.isEmpty(), "No bus specification in path between ports " + mapper.getFQNOrEmpty(mappingFrom) + " and " + mapper.getFQNOrEmpty(mappingTo)); return inPortList; } private void filterPathTail(OutPort mappingFrom, Iterator<Object> unfilteredPathIterator, Deque<Entry<OutPort, InPort>> inPortList) { OutPort lastOutPort = mappingFrom; for (; unfilteredPathIterator.hasNext();) { Object object = unfilteredPathIterator.next(); if (object instanceof OutPort) { OutPort nextOutPort = (OutPort) object; filterNextConnectionInPath(inPortList, lastOutPort, nextOutPort); lastOutPort = nextOutPort; } } } private void filterNextConnectionInPath(Deque<Entry<OutPort, InPort>> inPortList, OutPort lastOutPort, OutPort nextOutPort) { Block outportContainer = nextOutPort.getContainer(); if (outportContainer instanceof BusSpecification) { InPort connectedInPort = findConnectedInPortOnNextContainer(lastOutPort, outportContainer); checkState(connectedInPort != null, "Invalid path, last outport is not connected to any inports!"); inPortList.add(Maps.immutableEntry(lastOutPort, connectedInPort)); } } private InPort findConnectedInPortOnNextContainer(OutPort lastOutPort, Block outportContainer) { InPort connectedInPort = null; for (InPort inPort : outportContainer.getInports()) { if (mapper.getConnectedOutPort(inPort).equals(lastOutPort)) { checkState(connectedInPort == null, "Multiple inports connected to the same outport!"); connectedInPort = inPort; } } return connectedInPort; } private void printPaths(List<Deque<Entry<OutPort, InPort>>> paths) { if (mapper.isDebugging()) { StringBuilder sb = new StringBuilder(); for (Deque<Entry<OutPort, InPort>> list : paths) { sb.append("Found path:\n"); for (Entry<OutPort, InPort> entry : list) { sb.append(mapper.getFQNOrEmpty(entry.getKey()) + " to " + mapper.getFQNOrEmpty(entry.getValue())); sb.append(" --- line: " + mapper.getCollisionFreeLineName(entry.getValue()) + "\n"); } } mapper.logDebug(sb.toString()); } } private String findMappingStringFromPath(OutPort mappingTo, Deque<Entry<OutPort, InPort>> path) { PathMappingFragmentStore store = new PathMappingFragmentStore(); for (Entry<OutPort, InPort> entry : path) { processNextPathEntry(entry, store); } return store.pathBuilder.toString(); } private void processNextPathEntry(Entry<OutPort, InPort> entry, PathMappingFragmentStore store) { InPort inport = entry.getValue(); OutPort outport = entry.getKey(); String currentFragment = store.pathBuilder.toString(); store.pathFragments.put(outport, currentFragment); mapper.logDebug("Adding fragment: %s for %s", currentFragment, mapper.getFQNOrEmpty(outport)); String lineName = mapper.getCollisionFreeLineName(inport); store.collisionFreeNames.put(outport, lineName); Block container = inport.getContainer(); if (container instanceof BusCreator) { // signal will be bundled, append name of incoming line appendConnectionName(store.pathBuilder, lineName); } else if (container instanceof BusSelector) { // find the mapping that corresponds to an outport in pathFragments BusSelector busSelector = (BusSelector) container; Entry<OutPort, String> fragmentForMapping = findFragmentForMapping(store.pathFragments, busSelector); mapper.logDebug("Retrieved fragment: %s for %s", fragmentForMapping.getValue(), mapper.getFQNOrEmpty(busSelector)); store.pathBuilder = new StringBuilder(fragmentForMapping.getValue()); if (busSelector.isOutputAsBus()) { String storedLineName = store.collisionFreeNames.get(fragmentForMapping.getKey()); checkState(storedLineName != null, "No fragment stored for outport in selector mapping!"); appendConnectionName(store.pathBuilder, storedLineName); } } else { throw new IllegalStateException("Filtered path cannot include other type of elements! But encountered: " + container); } } private void appendConnectionName(StringBuilder pathBuilder, String lineName) { if (pathBuilder.length() > 0) { pathBuilder.insert(0, "."); } pathBuilder.insert(0, lineName); } private Entry<OutPort, String> findFragmentForMapping(Map<OutPort, String> pathFragments, BusSelector busSelector) { for (BusSignalMapping m : busSelector.getMappings()) { OutPort from = m.getMappingFrom(); if (pathFragments.containsKey(from)) { // replace current path with stored return Maps.immutableEntry(from, pathFragments.get(from)); } } // if there is none, the path is invalid (selector selects from farther back) throw new IllegalStateException("Cannot find path fragment for mapping in bus selector!"); } private static class PathMappingFragmentStore { StringBuilder pathBuilder = new StringBuilder(); Map<OutPort, String> pathFragments = Maps.newHashMap(); Map<OutPort, String> collisionFreeNames = Maps.newHashMap(); } }