/******************************************************************************* * Copyright (c) 2010 protos software gmbh (http://www.protos.de). * 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: * Thomas Schuetz and Henrik Rentz-Reichert (initial contribution) * *******************************************************************************/ package org.eclipse.etrice.generator.builder; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.etrice.core.room.ActorClass; import org.eclipse.etrice.core.room.ActorContainerRef; import org.eclipse.etrice.core.room.ActorRef; import org.eclipse.etrice.core.room.Binding; import org.eclipse.etrice.core.room.BindingEndPoint; import org.eclipse.etrice.core.room.RefSAPoint; import org.eclipse.etrice.core.room.SubSystemClass; import org.eclipse.etrice.core.room.ExternalPort; import org.eclipse.etrice.core.room.LayerConnection; import org.eclipse.etrice.core.room.Port; import org.eclipse.etrice.core.room.ProtocolClass; import org.eclipse.etrice.core.room.RelaySAPoint; import org.eclipse.etrice.core.room.RoomModel; import org.eclipse.etrice.core.room.SAPRef; import org.eclipse.etrice.core.room.SAPoint; import org.eclipse.etrice.core.room.SPPRef; import org.eclipse.etrice.core.room.SPPoint; import org.eclipse.etrice.core.room.ServiceImplementation; import org.eclipse.etrice.core.room.SubSystemRef; import org.eclipse.etrice.generator.etricegen.ActorInstance; import org.eclipse.etrice.generator.etricegen.BindingInstance; import org.eclipse.etrice.generator.etricegen.ConnectionInstance; import org.eclipse.etrice.generator.etricegen.Counter; import org.eclipse.etrice.generator.etricegen.ETriceGenFactory; import org.eclipse.etrice.generator.etricegen.ExpandedActorClass; import org.eclipse.etrice.generator.etricegen.IDiagnostician; import org.eclipse.etrice.generator.etricegen.PortInstance; import org.eclipse.etrice.generator.etricegen.PortKind; import org.eclipse.etrice.generator.etricegen.Root; import org.eclipse.etrice.generator.etricegen.SAPInstance; import org.eclipse.etrice.generator.etricegen.SPPInstance; import org.eclipse.etrice.generator.etricegen.ServiceImplInstance; import org.eclipse.etrice.generator.etricegen.StructureInstance; import org.eclipse.etrice.generator.etricegen.SubSystemInstance; import org.eclipse.etrice.generator.etricegen.impl.StructureInstanceImpl; /** * A class for the creation of an intermediate model combining all information needed by * the code generator. * * @author Henrik Rentz-Reichert * */ public class InstanceModelBuilder { /** * the first object id used for the {@link org.eclipse.etrice.core.etrice.runtime.messaging.Address Address}es s of runtime instances */ private static final int OBJ_ID_OFFSET = 100; /** * a set containing all relay ports for fast frequent access to this information */ private HashSet<Port> relayPorts = new HashSet<Port>(); /** * an instance of a logger */ private ILogger logger; /** * an isntance of a diagnostician */ private IDiagnostician diagnostician; /** * the only constructor takes a logger and a diagnostician as arguments * @param logger * @param diagnostician */ public InstanceModelBuilder(ILogger logger, IDiagnostician diagnostician) { this.logger = logger; this.diagnostician = diagnostician; } // TODOHRR: combine a RoomProject (.room_proj) with RoomModels (.room) /** * Creates a model of all instances for all sub systems. * Actor instances are created in a hierarchical structure. * There is only one list of port per actor instances. * Ports have a type attribute (relay, intern, external). * Bindings connect port instances. And since ports are * instances it is possible to have pointers back to the * bindings. * After creating the instance tree ports are connected to * their peers. * Similar SAPs, Services and layer conenctions are treated. * Finally expanded (xp) actor classes are created which * contain also inherited state graph items and where RefinedStates * are removed and their contents is relocated. * * @param models * @return the root of the newly created instance model */ public Root createInstanceModel(List<RoomModel> models) { Root root = ETriceGenFactory.eINSTANCE.createRoot(); root.getModels().addAll(models); determineRelayPorts(root); for (RoomModel model : models) { for (SubSystemClass comp : model.getSubSystemClasses()) { root.getSubSystems().add(createSubSystemInstance(comp)); } } connectPorts(root); checkPortMultiplicity(root); connectServices(root); createExpandedActorClasses(root); return root; } /** * Connect all services hierarchically. This finally connects SAPs with corresponding services * according to layer connections. * * @param root */ private void connectServices(Root root) { createServiceMappings(root); bindSAPs(root); } /** * Hierarchically create service mappings at each starting point of layer connections (root level). * * @param root */ private void createServiceMappings(Root root) { for (SubSystemInstance comp : root.getSubSystems()) { createServiceMappings(comp); } } /** * Hierarchically create service mappings at each starting point of layer connections (recursively for all structure instances). * * @see createServiceMappings * @param si */ private void createServiceMappings(StructureInstance si) { for (ConnectionInstance ci : si.getConnections()) { if (ci.getFromSPP()==null) { // this connection originates at an actor instance addService(ci.getFromAI(), ci); } else if (ci.getFromSPP().getIncoming().isEmpty()) { // this connection originates at an SPP instance which is not connected outside addService(si, ci); } } // recursive call for all children for (ActorInstance child : si.getInstances()) { createServiceMappings(child); } } /** * Determines a connected services and attaches it to the protocol2service mapping * of a structure instance * * @param si * @param ci */ private void addService(StructureInstance si, ConnectionInstance ci) { assert(si instanceof StructureInstanceImpl): "unknown implementation "+si.eClass().getName(); StructureInstanceImpl sii = (StructureInstanceImpl) si; ProtocolClass pc = ci.getToSPP().getSpp().getProtocol(); if (sii.protocol2service.get(pc)!=null) { // this protocol is already handled on this level EObject obj = null; if (si instanceof ActorInstance) obj = ((ActorInstance)si).getActorClass(); else if (si instanceof SubSystemInstance) obj = ((SubSystemInstance)si).getSubSystemClass(); else obj = si; diagnostician.error("A service can only be offered once per actor instance, consider pushing one down to a contained actor!", obj); } else { if (ci.getFromSPP()!=null && ci.getFromSPP().getSpp().getProtocol()!=pc) { diagnostician.error("Layer connection must connect same protocols!", ci.getConnection()); } else { // now we follow the layer connections while(true) { SPPInstance sppi = ci.getToSPP(); if (sppi.getOutgoing()==null) { // we reached the end, find the ServiceImplementation boolean found = false; if (sppi.eContainer() instanceof ActorInstance) { ActorInstance implementor = (ActorInstance) sppi.eContainer(); for (ServiceImplInstance svc : implementor.getServices()) { if (svc.getSvcImpl().getSpp()==sppi.getSpp()) { found = true; sii.protocol2service.put(pc, svc); } } } else { assert(false); } if (!found) { diagnostician.error("An SPP mus be connected by a layer connection or implemented by a ServiceImplementation!", sppi.getSpp()); } return; } else { ci = sppi.getOutgoing(); if (ci.getToSPP().getSpp().getProtocol()!=pc) { diagnostician.error("Layer connection must connect same protocols!", ci.getConnection()); return; } } } } } } /** * Connect a SAP to its service (root level) * * @param root */ private void bindSAPs(Root root) { for (SubSystemInstance comp : root.getSubSystems()) { bindSAPs(comp); setServiceObjectIDs(comp, comp.getObjCounter()); } } /** * Connect a SAP to its service (recursively for all structure instances). * * @param si */ private void bindSAPs(StructureInstance si) { for (SAPInstance sap : si.getSaps()) { bindSAP(si, sap); } // recursive call for all children for (ActorInstance child : si.getInstances()) { bindSAPs(child); } } /** * Do the actual binding of a SAP. * * @param si * @param sap */ private void bindSAP(StructureInstance si, SAPInstance sap) { assert(si instanceof StructureInstanceImpl); StructureInstanceImpl sii = (StructureInstanceImpl) si; // walk up the container hierarchy until the sap is satisfied do { ServiceImplInstance svc = sii.protocol2service.get(sap.getSap().getProtocol()); if (svc!=null) { sap.getPeers().add(svc); svc.getPeers().add(sap); return; } if (sii.eContainer() instanceof StructureInstanceImpl) sii = (StructureInstanceImpl) sii.eContainer(); else sii = null; } while (sii!=null); diagnostician.error("SAP not satisfied!", sap.getSap()); } /** * Finally recursively the Address object IDs are calculated and assigned * @param si * @param counter */ private void setServiceObjectIDs(StructureInstance si, Counter counter) { for (ServiceImplInstance svc : si.getServices()) { svc.setObjId(counter.getAndIncrementCount(svc.getPeers().size())); } // recursive call for all children for (ActorInstance child : si.getInstances()) { setServiceObjectIDs(child, counter); } } /** * for efficiency reasons we create a set holding all relay ports * @param root - the root object */ private void determineRelayPorts(Root root) { for (RoomModel model : root.getModels()) { for (ActorClass ac : model.getActorClasses()) { for (Port port : ac.getIfPorts()) { boolean external = false; for (ExternalPort ep : ac.getExtPorts()) { if (ep.getIfport()==port) { external = true; break; } } if (!external) { relayPorts.add(port); // check whether relay port is multiply connected int count = 0; for (Binding b : ac.getBindings()) { if (b.getEndpoint1().getPort()==port) ++count; if (b.getEndpoint2().getPort()==port) ++count; } if (count>1) diagnostician.error("relay port is multiply connected inside its actor class", port); } } } } } /** * hierarchically (i.e. recursively) creates all instances implied by this component * @param comp - the component class * @return the newly created hierarchy of instances */ private SubSystemInstance createSubSystemInstance(SubSystemClass comp) { logger.logInfo("InstanceModelBuilder: creating component instance from "+comp.getName()); SubSystemInstance instance = ETriceGenFactory.eINSTANCE.createSubSystemInstance(); instance.setName(comp.getName()); Counter objCounter = ETriceGenFactory.eINSTANCE.createCounter(); objCounter.setCounter(OBJ_ID_OFFSET); instance.setObjCounter(objCounter); instance.setObjId(objCounter.getAndIncrementCount()); instance.setSubSystemClass(comp); // TODOHRR: enumerate object ids per thread for (ActorRef ar : comp.getActorRefs()) { instance.getInstances().add(recursivelyCreateActorInstances(objCounter, ar)); } // bindings are handled now since port instances of sub-actor instances are available createBindingInstances(instance, comp.getBindings()); createConnectionInstances(instance, comp.getConnections()); return instance; } /** * hierarchically (i.e. recursively) creates all instances implied by this actor * @param instance - the root component instance * @param aref - create the instance sub-tree of this actor reference * @return the newly created actor instance */ private ActorInstance recursivelyCreateActorInstances(Counter objCounter, ActorRef aref) { logger.logInfo("InstanceModelBuilder: creating actor instance "+aref.getName()+" from "+aref.getType().getName()); ActorInstance ai = ETriceGenFactory.eINSTANCE.createActorInstance(); ai.setName(aref.getName()); ai.setObjId(objCounter.getAndIncrementCount()); ActorClass ac = aref.getType(); ai.setActorClass(ac); // create a list of super classes, super first, sub-classes last LinkedList<ActorClass> classes = new LinkedList<ActorClass>(); classes.addFirst(ac); while (ac.getBase()!=null) { ac = ac.getBase(); classes.addFirst(ac); } // create instances for super classes recursively (ports, actor refs and bindings) // super classes first ensures that actor refs are present when bindings are created for (ActorClass acl : classes) { // first we add our port instances to have them numbered subsequently createPortInstances(objCounter, ai, acl); createServiceRelatedInstances(objCounter, ai, acl); // recurse down into sub-actors for (ActorRef ar : acl.getActorRefs()) { ai.getInstances().add(recursivelyCreateActorInstances(objCounter, ar)); } } for (ActorClass acl : classes) { // bindings are handled now since port instances of sub-actor instances are available createBindingInstances(ai, acl.getBindings()); createConnectionInstances(ai, acl.getConnections()); } return ai; } /** * create port instances for every kind of port * @param objCounter - for unique object id used for address creation * @param ai - the currently considered actor instance * @param ac - the actor class (might be a base class) */ private void createPortInstances(Counter objCounter, ActorInstance ai, ActorClass ac) { for (ExternalPort port : ac.getExtPorts()) { PortInstance pi = ETriceGenFactory.eINSTANCE.createPortInstance(); pi.setName(port.getIfport().getName()); // replicated ports have subsequent object IDs pi.setObjId(objCounter.getAndIncrementCount(port.getIfport().getMultiplicity())); pi.setPort(port.getIfport()); pi.setKind(PortKind.EXTERNAL); ai.getPorts().add(pi); } for (Port port : ac.getIntPorts()) { PortInstance pi = ETriceGenFactory.eINSTANCE.createPortInstance(); pi.setName(port.getName()); // replicated ports have subsequent object IDs pi.setObjId(objCounter.getAndIncrementCount(port.getMultiplicity())); pi.setPort(port); pi.setKind(PortKind.INTERNAL); ai.getPorts().add(pi); } for (Port port : ac.getIfPorts()) { if (relayPorts.contains(port)) { PortInstance pi = ETriceGenFactory.eINSTANCE.createPortInstance(); pi.setName(port.getName()); // relay ports are not instantiated and thus have no object ID //pi.setObjId(instance.getAndIncrementObjCount()); pi.setPort(port); pi.setKind(PortKind.RELAY); ai.getPorts().add(pi); } } } /** * create sap, spp and service instances * @param objCounter - for unique object id used for address creation * @param ai - the currently considered actor instance * @param ac - the actor class (might be a base class) */ private void createServiceRelatedInstances(Counter objCounter, ActorInstance ai, ActorClass ac) { for (SAPRef sap : ac.getStrSAPs()) { SAPInstance si = ETriceGenFactory.eINSTANCE.createSAPInstance(); si.setName(sap.getName()); si.setObjId(objCounter.getAndIncrementCount()); si.setSap(sap); ai.getSaps().add(si); } for (SPPRef sap : ac.getIfSPPs()) { SPPInstance si = ETriceGenFactory.eINSTANCE.createSPPInstance(); si.setName(sap.getName()); // SPPs are not instantiated and thus need no object ID //si.setObjId(objCounter.getAndIncrementCount()); si.setSpp(sap); ai.getSpps().add(si); } for (ServiceImplementation svcimpl : ac.getServiceImplementations()) { ServiceImplInstance sii = ETriceGenFactory.eINSTANCE.createServiceImplInstance(); sii.setName(svcimpl.getSpp().getName()); //will set the object ID later when we know all connected saps //sii.setObjId(objCounter.getAndIncrementCount()); sii.setSvcImpl(svcimpl); ai.getServices().add(sii); } } /** * create binding instances. Since bindings connect port instances the ports can point back to their bindings * using EOpposite * @param ai - create bindings for this actor instance * @param bindings - a list of bindings */ private void createBindingInstances(StructureInstance ai, EList<Binding> bindings) { for (Binding bind : bindings) { BindingInstance bi = ETriceGenFactory.eINSTANCE.createBindingInstance(); if (bind.getEndpoint1().getActorRef()==null && bind.getEndpoint2().getActorRef()!=null) { bi.getPorts().add(getPortInstance(ai, bind.getEndpoint1())); bi.getPorts().add(getPortInstance(ai, bind.getEndpoint2())); } else if (bind.getEndpoint1().getActorRef()!=null && bind.getEndpoint2().getActorRef()==null) { bi.getPorts().add(getPortInstance(ai, bind.getEndpoint2())); bi.getPorts().add(getPortInstance(ai, bind.getEndpoint1())); } else if (bind.getEndpoint1().getActorRef()!=null && bind.getEndpoint2().getActorRef()!=null) { bi.getPorts().add(getPortInstance(ai, bind.getEndpoint1())); bi.getPorts().add(getPortInstance(ai, bind.getEndpoint2())); } else { diagnostician.error("binding connects two ports of the same actor", bind, -1); } ai.getBindings().add(bi); } } /** * Create layer connection instances. * @param si - create layer connections for this actor instance * @param connections - a list of layer connections */ private void createConnectionInstances(StructureInstance si, EList<LayerConnection> connections) { for (LayerConnection lc : connections) { ConnectionInstance ci = ETriceGenFactory.eINSTANCE.createConnectionInstance(); ci.setConnection(lc); SAPoint from = lc.getFrom(); if (from instanceof RefSAPoint) { if (((RefSAPoint)from).getRef() instanceof ActorRef) { ActorInstance fromAI = getActorInstance(si, ((ActorRef)((RefSAPoint)from).getRef())); ci.setFromAI(fromAI); } else { //TODOHRR: handle SubSystemRef System.err.println("error"); } } else if (from instanceof RelaySAPoint) { SPPInstance sppi = getSPPInstance(si, null, ((RelaySAPoint)from).getRelay()); if (sppi.getOutgoing()!=null) diagnostician.error("SPPRef has several outgoing layer connections!", sppi.getSpp()); ci.setFromSPP(sppi); } else { assert(false): "unknown type of "+from.eClass().getName(); } SPPoint to = lc.getTo(); SPPInstance sppi = getSPPInstance(si, to.getRef(), to.getService()); ci.setToSPP(sppi); si.getConnections().add(ci); } } /** * Returns the endpoint of a layer connection. * * @param si * @param ar * @param spp * @return */ private SPPInstance getSPPInstance(StructureInstance si, ActorContainerRef ar, SPPRef spp) { if (ar==null) { for (SPPInstance sppi : si.getSpps()) { if (sppi.getSpp()==spp) return sppi; } } else { if (ar instanceof ActorRef) { ActorInstance subai = getActorInstance(si, (ActorRef)ar); if (subai!=null) return getSPPInstance(subai, null, spp); } else if (ar instanceof SubSystemRef) { // TODOHRR: handle SubSystemRef } } return null; } /** * Returns an actor instances corresponding to an ActorRef. * * @param si * @param ar * @return */ private ActorInstance getActorInstance(StructureInstance si, ActorRef ar) { for (ActorInstance subai : si.getInstances()) { if (subai.getName().equals(ar.getName())) { return subai; } } return null; } /** * get a port instance for a binding endpoint * @param si - consider this actor instance * @param be - the binding endpoint to match * @return the port instance found */ private PortInstance getPortInstance(StructureInstance si, BindingEndPoint be) { if (be.getActorRef()==null) { for (PortInstance pi : si.getPorts()) { if (pi.getPort()==be.getPort()) { if (pi.getKind()==PortKind.EXTERNAL) diagnostician.error("binding connects external end port to sub-actor interface", be.eContainer(), -1); return pi; } } } else { for (ActorInstance subai : si.getInstances()) { if (subai.getName().equals(be.getActorRef().getName())) { for (PortInstance pi : subai.getPorts()) { if (pi.getPort()==be.getPort()) { if (pi.getKind()==PortKind.INTERNAL) diagnostician.error("binding connects to sub-actor internal end port", be.eContainer(), -1); return pi; } } } } } return null; } /** * set peer ports in the whole instance model * @param root */ private void connectPorts(Root root) { TreeIterator<EObject> it = root.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof ActorInstance) { for (PortInstance pi : ((ActorInstance) obj).getPorts()) { if (pi.getKind()!=PortKind.RELAY) { List<PortInstance> peers = getFinalPeers(pi, null); pi.getPeers().addAll(peers); // we don't have to add pi to its peer.peers since we do that once we reach there } } } } } /** * determine final peers of an end port * @param pi - a end port * @param from - the binding from which we reached pi or null if start * @return a list of final peer port instances (end ports themselves) */ private List<PortInstance> getFinalPeers(PortInstance pi, BindingInstance from) { List<PortInstance> peers = new LinkedList<PortInstance>(); for (BindingInstance bi : pi.getBindings()) { if (bi==from) // skip the binding where we came from continue; if (from!=null && from.eContainer()==bi.eContainer()) // the container of a binding instance is a StructureInstance // by this we make sure that we go from inside to outside or vice versa continue; PortInstance end = (bi.getPorts().get(0)!=pi)? bi.getPorts().get(0) : bi.getPorts().get(1); if (end.getKind()==PortKind.RELAY) // continue recursion peers.addAll(getFinalPeers(end, bi)); else // this is a final peer peers.add(end); } return peers; } /** * check that the number of peer ports does not exceed the multiplicity of a port * @param root */ private void checkPortMultiplicity(Root root) { TreeIterator<EObject> it = root.eAllContents(); while (it.hasNext()) { EObject obj = it.next(); if (obj instanceof ActorInstance) { ActorInstance ai = (ActorInstance) obj; for (PortInstance pi : ai.getPorts()) { if (pi.getKind()!=PortKind.RELAY) { if (pi.getBindings().size()>pi.getPort().getMultiplicity()) diagnostician.error("number of peers "+pi.getBindings().size() + " of port "+pi.getName() +" exceeds multiplicity "+pi.getPort().getMultiplicity() +" in instance "+ai.getPath(), pi); } } } } } /** * expanded (xp) actor classes are created which * contain also inherited features and where RefinedStates * are removed and their contents is relocated. * @param root - the model root */ private void createExpandedActorClasses(Root root) { for (ActorClass ac : root.getUsedActorClasses()) { root.getXpActorClasses().add(createExpandedActorClass(ac)); } } /** * create an expanded actor class from the original model class * @param ac - the original model class * @return - the newly created expanded actor class */ private ExpandedActorClass createExpandedActorClass(ActorClass ac) { logger.logInfo("InstanceModelBuilder: creating expanded actor class from "+ac.getName() +" of "+((RoomModel)ac.eContainer()).getName()); ExpandedActorClass xpac = ETriceGenFactory.eINSTANCE.createExpandedActorClass(); xpac.setName(ac.getName()); xpac.setActorClass(ac); xpac.setAbstract(ac.isAbstract()); xpac.prepare(diagnostician); return xpac; } }