| /******************************************************************************* |
| * 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 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * CONTRIBUTORS: |
| * Thomas Schuetz and Henrik Rentz-Reichert (initial contribution) |
| * Eyrak Paen |
| * |
| *******************************************************************************/ |
| |
| package org.eclipse.etrice.core.validation; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EStructuralFeature.Setting; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.etrice.core.common.base.Annotation; |
| import org.eclipse.etrice.core.common.base.BasePackage; |
| import org.eclipse.etrice.core.common.base.BooleanLiteral; |
| import org.eclipse.etrice.core.common.base.Import; |
| import org.eclipse.etrice.core.common.base.LiteralType; |
| import org.eclipse.etrice.core.common.base.util.ImportHelpers; |
| import org.eclipse.etrice.core.common.validation.ValidationHelpers; |
| import org.eclipse.etrice.core.common.validation.ValidationHelpers.NamedObject; |
| import org.eclipse.etrice.core.common.validation.ValidationHelpers.NamedObjectList; |
| import org.eclipse.etrice.core.fsm.fSM.ComponentCommunicationType; |
| import org.eclipse.etrice.core.fsm.fSM.FSMPackage; |
| import org.eclipse.etrice.core.fsm.fSM.MessageFromIf; |
| import org.eclipse.etrice.core.fsm.validation.FSMValidationUtilXtend.Result; |
| import org.eclipse.etrice.core.room.ActorClass; |
| import org.eclipse.etrice.core.room.ActorContainerClass; |
| import org.eclipse.etrice.core.room.ActorInstanceMapping; |
| import org.eclipse.etrice.core.room.ActorRef; |
| import org.eclipse.etrice.core.room.Attribute; |
| import org.eclipse.etrice.core.room.Binding; |
| import org.eclipse.etrice.core.room.BindingEndPoint; |
| import org.eclipse.etrice.core.room.CommunicationType; |
| import org.eclipse.etrice.core.room.CompoundProtocolClass; |
| import org.eclipse.etrice.core.room.DataClass; |
| import org.eclipse.etrice.core.room.EnumerationType; |
| import org.eclipse.etrice.core.room.ExternalPort; |
| import org.eclipse.etrice.core.room.InterfaceItem; |
| import org.eclipse.etrice.core.room.LayerConnection; |
| import org.eclipse.etrice.core.room.LogicalSystem; |
| import org.eclipse.etrice.core.room.Message; |
| import org.eclipse.etrice.core.room.MessageData; |
| import org.eclipse.etrice.core.room.Operation; |
| import org.eclipse.etrice.core.room.Port; |
| import org.eclipse.etrice.core.room.PortClass; |
| import org.eclipse.etrice.core.room.PrimitiveType; |
| import org.eclipse.etrice.core.room.ProtocolClass; |
| import org.eclipse.etrice.core.room.RefPath; |
| import org.eclipse.etrice.core.room.ReferenceType; |
| import org.eclipse.etrice.core.room.RelaySAPoint; |
| import org.eclipse.etrice.core.room.RoomAnnotationTargetEnum; |
| import org.eclipse.etrice.core.room.RoomClass; |
| import org.eclipse.etrice.core.room.RoomElement; |
| import org.eclipse.etrice.core.room.RoomModel; |
| import org.eclipse.etrice.core.room.RoomPackage; |
| import org.eclipse.etrice.core.room.ServiceImplementation; |
| import org.eclipse.etrice.core.room.StandardOperation; |
| import org.eclipse.etrice.core.room.StructureClass; |
| import org.eclipse.etrice.core.room.SubSystemClass; |
| import org.eclipse.etrice.core.room.util.RoomHelpers; |
| import org.eclipse.etrice.generator.base.io.IModelPath; |
| import org.eclipse.etrice.generator.base.io.IModelPathProvider; |
| import org.eclipse.xtext.naming.IQualifiedNameConverter; |
| import org.eclipse.xtext.naming.IQualifiedNameProvider; |
| import org.eclipse.xtext.naming.QualifiedName; |
| import org.eclipse.xtext.resource.IEObjectDescription; |
| import org.eclipse.xtext.validation.Check; |
| import org.eclipse.xtext.validation.CheckType; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.FluentIterable; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.inject.Inject; |
| |
| public class RoomJavaValidator extends AbstractRoomJavaValidator { |
| |
| @Inject protected RoomHelpers roomHelpers; |
| |
| @Inject protected ValidationUtil validationUtil; |
| |
| @Inject protected IQualifiedNameProvider fqnProvider; |
| |
| @Inject protected IQualifiedNameConverter nameConverter; |
| |
| @Inject private IModelPathProvider modelPathProvider; |
| |
| @Inject ImportHelpers importHelpers; |
| |
| /* message strings */ |
| public static final String OPTIONAL_REFS_HAVE_TO_HAVE_MULTIPLICITY_ANY = "optional refs have to have multiplicity any [*]"; |
| public static final String MULTIPLICITY_ANY_REQUIRES_OPTIONAL = "multiplicity any [*] requires optional"; |
| public static final String A_REPLICATED_PORT_MUST_HAVE_AT_MOST_ONE_REPLICATED_PEER = "a replicated port must have at most one replicated peer (with arbitrary multiplicity each)"; |
| |
| /* tags for quick fixes */ |
| public static final String WRONG_MODEL_NAME = "RoomJavaValidator.WrongModelName"; |
| public static final String THREAD_MISSING = "RoomJavaValidator.ThreadMissing"; |
| public static final String DUPLICATE_ACTOR_INSTANCE_MAPPING = "RoomJavaValidator.DuplicateActorInstanceMapping"; |
| public static final String WRONG_NAMESPACE = "RoomJavaValidator.WrongNamespace"; |
| public static final String CIRCULAR_CONTAINMENT = "RoomJavaValidator.CircularContainment"; |
| public static final String ACTOR_REF_CHANGE_REF_TYPE_TO_FIXED_OR_MULT_TO_ANY = "RoomJavaValidator.ActorRefChangeRefTypeToFixed"; |
| public static final String ACTOR_REF_CHANGE_REF_TYPE_TO_OPTIONAL = "RoomJavaValidator.ActorRefChangeRefTypeToOptional"; |
| public static final String CHANGE_DESTRUCTOR_NAME = "RoomJavaValidator.ChangeDestructorName"; |
| public static final String CHANGE_CONSTRUCTOR_NAME = "RoomJavaValidator.ChangeConstructorName"; |
| public static final String INVALID_ANNOTATION_TARGET = "RoomJavaValidator.InvalidAnnotationTarget"; |
| public static final String OPERATION_MISSING_OVERRIDE = "RoomJavaValidator.OperationMissingOverride"; |
| public static final String OPERATION_EXTRANEOUS_OVERRIDE = "RoomJavaValidator.OperationExtraneousOverride"; |
| public static final String INCONSISTENT_COMMUNICATION_TYPE = "RoomJavaValidator.InconsistentCommType"; |
| public static final String DEPRECATED_MESSAGE_DATA_NAME = "RoomJavaValidator.DeprecatedMessageDataName"; |
| |
| @Check |
| public void checkRoomModelName(RoomModel roomModel) { |
| QualifiedName modelName = fqnProvider.getFullyQualifiedName(roomModel); |
| if(modelName != null) { |
| Resource resource = roomModel.eResource(); |
| IModelPath modelpath = modelPathProvider.get(resource); |
| modelpath.getQualifiedName(resource.getURI()).ifPresent(fileName -> { |
| if(!fileName.equals(modelName)) { |
| error("model name doesn't match its file name and its location on the modelpath", RoomPackage.Literals.ROOM_MODEL__NAME, |
| WRONG_MODEL_NAME, nameConverter.toString(modelName), nameConverter.toString(fileName)); |
| } |
| }); |
| } |
| } |
| |
| @Check |
| public void checkRoomImportedNamespace(Import imp) { |
| QualifiedName importedFQN = importHelpers.toFQN(imp); |
| if(importedFQN == null) |
| return; |
| |
| Predicate<IEObjectDescription> nameMatcher = new Predicate<IEObjectDescription>() { |
| |
| @Override |
| public boolean apply(IEObjectDescription input) { |
| return importedFQN.equals(input.getQualifiedName()); |
| } |
| |
| }; |
| Predicate<IEObjectDescription> candidateMatcher = new Predicate<IEObjectDescription>() { |
| |
| @Override |
| public boolean apply(IEObjectDescription input) { |
| return input.getEClass() == RoomPackage.Literals.ROOM_MODEL; |
| } |
| |
| }; |
| Predicate<IEObjectDescription> allowedClasses = new Predicate<IEObjectDescription>() { |
| |
| @Override |
| public boolean apply(IEObjectDescription input) { |
| return input.getEClass() == RoomPackage.Literals.ROOM_MODEL || |
| input.getEClass() == BasePackage.Literals.ANNOTATION_TYPE || |
| RoomPackage.Literals.ROOM_CLASS.isSuperTypeOf(input.getEClass()); |
| } |
| |
| }; |
| Optional<List<IEObjectDescription>> importCandidates = importHelpers.getImportedObjectsFor(imp, candidateMatcher); |
| if(!importCandidates.isPresent()) { |
| return; |
| } |
| List<IEObjectDescription> candidates = importCandidates.get(); |
| Optional<IEObjectDescription> exactMatch = Iterables.tryFind(candidates, Predicates.and(nameMatcher, candidateMatcher)).toJavaUtil(); |
| if(exactMatch.isPresent()) { |
| EObject importedElement = exactMatch.get().getEObjectOrProxy(); |
| if(importedElement instanceof RoomElement && roomHelpers.findDeprecatedAnnotation((RoomElement) importedElement) != null) { |
| warning("Deprecated Element", null); |
| } |
| return; |
| } |
| |
| Set<String> candidatesNames = Sets.newLinkedHashSet(); |
| for(IEObjectDescription eObjDesc : candidates) { |
| candidatesNames.add(eObjDesc.getQualifiedName().toString() + ".*"); |
| } |
| |
| if(candidates.isEmpty()) { |
| error("no match for imported namespace", BasePackage.Literals.IMPORT__IMPORTED_NAMESPACE); |
| } else if(!Iterables.any(candidates, nameMatcher)) { |
| error("no match for imported namespace", BasePackage.Literals.IMPORT__IMPORTED_NAMESPACE, WRONG_NAMESPACE, candidatesNames.toArray(new String[0])); |
| } else if(!Iterables.any(Iterables.filter(candidates, nameMatcher), allowedClasses)) { |
| error("referenced model is not supported", BasePackage.Literals.IMPORT__IMPORTED_NAMESPACE, WRONG_NAMESPACE, candidatesNames.toArray(new String[0])); |
| } |
| } |
| |
| @Check |
| public void checkActorRef(ActorRef ar) { |
| if (ar.eContainer() instanceof ActorClass) { |
| ActorClass ac = (ActorClass) ar.eContainer(); |
| |
| if (roomHelpers.isReferencing(ar.getType(), ac)) { |
| error("Actor reference is circular", RoomPackage.eINSTANCE.getActorRef_Type()); |
| } |
| } |
| |
| // fixed actor ref must NOT be of abstract type |
| if (ar.getRefType()==ReferenceType.FIXED && ar.getType().isAbstract()) { |
| error("A (fixed) actor reference must not be of an abstract type", null); |
| } |
| |
| // check actor ref array upper bound |
| if (ar.getMultiplicity()<0) { |
| // multiplicity * requires optional |
| if (ar.getRefType()!=ReferenceType.OPTIONAL) |
| error(MULTIPLICITY_ANY_REQUIRES_OPTIONAL, |
| RoomPackage.eINSTANCE.getActorRef_RefType(), ACTOR_REF_CHANGE_REF_TYPE_TO_OPTIONAL); |
| } |
| else if (ar.getMultiplicity()>1) { |
| // fixed multiplicity requires fixed type |
| if (ar.getRefType()==ReferenceType.OPTIONAL) |
| error(OPTIONAL_REFS_HAVE_TO_HAVE_MULTIPLICITY_ANY, |
| RoomPackage.eINSTANCE.getActorRef_RefType(), ACTOR_REF_CHANGE_REF_TYPE_TO_FIXED_OR_MULT_TO_ANY, ar.getName()); |
| } |
| |
| // check actor ref array has ports with fixed multiplicity |
| if (ar!=null) { |
| ActorClass ac = ar.getType(); |
| if (ar.getMultiplicity()>1) { |
| for (Port p : ac.getInterfacePorts()) { |
| if (p.getMultiplicity()<0) { |
| //int idx = ((ActorContainerClass)ar.eContainer()).getActorRefs().indexOf(ar); |
| error("replicated actor must not have replicated port with arbitrary multiplicity", null); |
| } |
| } |
| } |
| } |
| } |
| |
| @Check |
| public void checkLayerConnectiontarget(LayerConnection lc) { |
| if (lc.getTo().getRef() instanceof ActorRef) |
| if (((ActorRef)lc.getTo().getRef()).getMultiplicity()>1) |
| error("layer connection must not connect to replicated actor", null); |
| } |
| |
| @Check |
| public void checkBaseClassesNotCircular(DataClass dc) { |
| if (dc==null) |
| return; |
| |
| if (roomHelpers.isCircularClassHierarchy(dc)) |
| error("Base classes are circular", RoomPackage.eINSTANCE.getDataClass_Base()); |
| } |
| |
| @Check |
| public void checkAttributeNotCircular(Attribute att) { |
| if (att.eContainer() instanceof ActorClass) |
| // no circle possible |
| return; |
| |
| if (att.eContainer() instanceof PortClass) |
| // no circle possible |
| return; |
| |
| if (!(att.eContainer() instanceof DataClass)) { |
| assert(false): "unexpected parent class"; |
| return; |
| } |
| |
| DataClass dc = (DataClass) att.eContainer(); |
| if (roomHelpers.isCircularClassHierarchy(dc)) |
| return; |
| |
| while (dc!=null) { |
| if (att.getType().getType()==dc && !att.getType().isRef()) { |
| error( |
| "Attribute type must not refer to own class or a super class", |
| RoomPackage.Literals.ATTRIBUTE__TYPE, |
| CIRCULAR_CONTAINMENT, |
| ""+att.getType().getType().getName().length() |
| ); |
| break; |
| } |
| |
| dc = dc.getBase(); |
| } |
| } |
| |
| @Check |
| public void checkAttribute(Attribute attr){ |
| if(attr.getType() == null) |
| error("missing type", attr, RoomPackage.Literals.ATTRIBUTE__TYPE); |
| // if(attr.getDefaultValueLiteral() != null) |
| // warning("deprecated, initialize in user code or .config instead", attr, RoomPackage.Literals.ATTRIBUTE__DEFAULT_VALUE_LITERAL); |
| } |
| |
| @Check |
| public void checkAttributeNoStringArray(Attribute att){ |
| // TODO-Enum |
| if(!att.getType().isRef() && att.getType().getType() instanceof PrimitiveType){ |
| PrimitiveType type = (PrimitiveType)att.getType().getType(); |
| if(type.getName().equalsIgnoreCase("string") && att.getSize() > 0) |
| error("string type must have multiplicity 0", RoomPackage.Literals.ATTRIBUTE__SIZE); |
| } |
| } |
| |
| @Check |
| public void checkBaseClassesNotCircular(ProtocolClass pc) { |
| if (pc==null) |
| return; |
| |
| if (roomHelpers.isCircularClassHierarchy(pc)) |
| error("Base classes are circular", RoomPackage.eINSTANCE.getProtocolClass_Base()); |
| } |
| |
| @Check |
| public void checkBaseClassesNotCircular(ActorClass ac) { |
| if (ac==null) |
| return; |
| |
| if (roomHelpers.isCircularClassHierarchy(ac)) |
| error("Base classes are circular", FSMPackage.eINSTANCE.getModelComponent_Base()); |
| } |
| |
| @Check |
| public void checkExecModelConsistent(ActorClass ac) { |
| if (roomHelpers.isCircularClassHierarchy(ac)) |
| return; |
| |
| ComponentCommunicationType commType = ac.getCommType(); |
| String acName = ac.getName(); |
| |
| switch (commType) { |
| case ASYNCHRONOUS: |
| break; |
| case DATA_DRIVEN: |
| break; |
| case EVENT_DRIVEN: |
| break; |
| case SYNCHRONOUS: |
| error("synchronous communication type not supported yet", FSMPackage.eINSTANCE.getModelComponent_CommType()); |
| } |
| |
| while (ac.getActorBase()!=null) { |
| ac = ac.getActorBase(); |
| |
| if (commType!=ac.getCommType()) { |
| error("Communication type '"+commType.getLiteral()+"' is not consistent with the " |
| +"base class '"+ac.getName()+"' which uses '"+ac.getCommType().getLiteral() |
| +"' (if not explicitly specified then 'eventdriven' is the default).", |
| FSMPackage.eINSTANCE.getModelComponent_CommType(), |
| |
| // remaining parameters for quick fix |
| INCONSISTENT_COMMUNICATION_TYPE, |
| ac.getCommType().getLiteral(), |
| acName); |
| } |
| } |
| } |
| |
| @Check |
| public void checkTopLevelRefinedStates(ActorClass ac) { |
| List<Result> errors = validationUtil.checkTopLevelRefinedStates(ac); |
| for (Result err : errors) { |
| error(err); |
| } |
| } |
| |
| @Check |
| public void checkSubSystem(SubSystemClass ssc){ |
| if (ssc.getActorRefs().isEmpty()) |
| warning("SubSystemClass must contain at least one ActorRef", RoomPackage.eINSTANCE.getActorContainerClass_ActorRefs()); |
| |
| if (ssc.getThreads().isEmpty()) |
| warning("at least one thread has to be defined", RoomPackage.Literals.SUB_SYSTEM_CLASS__THREADS, THREAD_MISSING, "LogicalThread defaultThread"); |
| |
| checkMappings(ssc.getActorInstanceMappings()); |
| } |
| |
| @Check |
| public void checkLogicalSystem(LogicalSystem ls){ |
| if (ls.getSubSystems().isEmpty()) |
| error("LogicalSystem must contain at least one SubSystemRef", RoomPackage.eINSTANCE.getLogicalSystem_SubSystems()); |
| } |
| |
| |
| @Check |
| public void checkActorInstanceMapping(ActorInstanceMapping aim) { |
| ActorContainerClass root = roomHelpers.getParentContainer(aim); |
| if (root != null && !root.eIsProxy()) { |
| RefPath path = aim.getPath(); |
| if (path != null) { |
| String invalidSegment = roomHelpers.checkPath(root, path); |
| if (invalidSegment != null) |
| error("no match for segment '" + invalidSegment + "'", |
| RoomPackage.Literals.ACTOR_INSTANCE_MAPPING__PATH); |
| else { |
| ActorRef aRef = roomHelpers.getLastActorRef(root, path); |
| if (aRef == null) |
| error("invalid actor reference", |
| RoomPackage.Literals.ACTOR_INSTANCE_MAPPING__PATH); |
| } |
| } |
| } |
| checkMappings(aim.getActorInstanceMappings()); |
| } |
| |
| private void checkMappings(EList<ActorInstanceMapping> actorInstanceMappings) { |
| HashSet<String> paths = new HashSet<String>(); |
| for (ActorInstanceMapping aim : actorInstanceMappings) { |
| if (!paths.add(aim.getPath().toString())) { |
| EObject parent = aim.eContainer(); |
| int idx = actorInstanceMappings.indexOf(aim); |
| error("duplicate mapping", parent, aim.eContainingFeature(), idx, DUPLICATE_ACTOR_INSTANCE_MAPPING); |
| } |
| } |
| } |
| |
| @Check |
| public void checkPortCompatibility(Binding bind) { |
| // don't validate if unresolved, this is already be an error |
| if(EcoreUtil.ExternalCrossReferencer.find(bind).keySet().stream().anyMatch(eObj -> eObj.eIsProxy())) |
| return; |
| // don't validate if protocols are missing or null (NPE), this is an error at the interface item |
| if(bind.getEndpoint1().getPort().getProtocol().eIsProxy() || bind.getEndpoint2().getPort().getProtocol().eIsProxy()) |
| return; |
| |
| Result result = validationUtil.isValid(bind); |
| if (!result.isOk()) { |
| error(result.getMsg(), bind, null); |
| } |
| } |
| |
| @Check |
| public void checkServiceCompatibility(LayerConnection conn) { |
| // don't validate if unresolved, this is already an error |
| if(EcoreUtil.ExternalCrossReferencer.find(conn).keySet().stream().anyMatch(eObj -> eObj.eIsProxy())) |
| return; |
| // don't validate if protocols are missing or null (NPE), this is an error at the interface item |
| if(conn.getFrom() instanceof RelaySAPoint && ((RelaySAPoint) conn.getFrom()).getRelay().getProtocol().eIsProxy()) |
| return; |
| if(conn.getTo().getService().getProtocol().eIsProxy()) |
| return; |
| |
| Result result = validationUtil.isValid(conn); |
| if (!result.isOk()) { |
| error(result.getMsg(), RoomPackage.eINSTANCE.getLayerConnection_From()); |
| } |
| } |
| |
| @Check |
| public void checkPortCommunicationCompatibility(ActorClass ac){ |
| if(ac.getCommType() == ComponentCommunicationType.SYNCHRONOUS){ |
| // not supported yet |
| return; |
| } |
| |
| // check external ports |
| List<InterfaceItem> extPorts = new ArrayList<InterfaceItem>(); |
| for(ExternalPort exPort : ac.getExternalPorts()) |
| extPorts.add(exPort.getInterfacePort()); |
| checkPortCommunicationCompatibility(ac, extPorts, RoomPackage.eINSTANCE.getActorClass_ExternalPorts()); |
| // check internal ports |
| checkPortCommunicationCompatibility(ac, ac.getInternalPorts(), RoomPackage.eINSTANCE.getActorClass_InternalPorts()); |
| // check SAPs |
| checkPortCommunicationCompatibility(ac, ac.getServiceAccessPoints(), RoomPackage.eINSTANCE.getActorClass_ServiceAccessPoints()); |
| // check service implementations |
| List<InterfaceItem> serviceImpls = new ArrayList<InterfaceItem>(); |
| for(ServiceImplementation si: ac.getServiceImplementations()) |
| serviceImpls.add(si.getSpp()); |
| checkPortCommunicationCompatibility(ac, serviceImpls, RoomPackage.eINSTANCE.getActorClass_ServiceImplementations()); |
| } |
| |
| private void checkPortCommunicationCompatibility(ActorClass ac, List<? extends InterfaceItem> items, EReference ref){ |
| boolean datadriven = ac.getCommType() == ComponentCommunicationType.DATA_DRIVEN; |
| boolean eventdriven = ac.getCommType() == ComponentCommunicationType.EVENT_DRIVEN; |
| boolean async = ac.getCommType() == ComponentCommunicationType.ASYNCHRONOUS; |
| //boolean synchronous = ac.getCommType() == ComponentCommunicationType.SYNCHRONOUS; |
| |
| for(InterfaceItem item : items){ |
| ProtocolClass pc = roomHelpers.getRoomProtocol(item); |
| if (pc!=null) |
| switch(pc.getCommType()){ |
| case DATA_DRIVEN: |
| if(!datadriven && !async) |
| error("ports with datadriven protocols not allowed", ref, items.indexOf(item)); |
| break; |
| case EVENT_DRIVEN: |
| if(!eventdriven && !async) |
| error("ports with eventdriven protocols not allowed", ref, items.indexOf(item)); |
| break; |
| case SYNCHRONOUS: |
| // not supported yet |
| } |
| } |
| } |
| |
| @Check |
| public void checkPort(Port port) { |
| if (port.getMultiplicity()==0) |
| error("multiplicity must not be 0", RoomPackage.eINSTANCE.getPort_Multiplicity()); |
| if (port.getMultiplicity()<-1) |
| error("multiplicity must be -1 or positive", RoomPackage.eINSTANCE.getPort_Multiplicity()); |
| if (port.getProtocol() instanceof ProtocolClass) |
| if (((ProtocolClass)port.getProtocol()).getCommType()==CommunicationType.DATA_DRIVEN && port.getMultiplicity()!=1) |
| error("multiplicity must be 1 for data driven ports", RoomPackage.eINSTANCE.getPort_Multiplicity()); |
| } |
| |
| @Check |
| public void checkProtocol(ProtocolClass pc) { |
| if (roomHelpers.isCircularClassHierarchy(pc)) |
| return; |
| |
| switch (pc.getCommType()) { |
| case DATA_DRIVEN: |
| if (pc.getBase()!=null && pc.getBase().getCommType()!=CommunicationType.DATA_DRIVEN) |
| error("super protocol has to have same communication type", RoomPackage.Literals.PROTOCOL_CLASS__COMM_TYPE); |
| if (roomHelpers.getAllMessages(pc, true).isEmpty()) |
| error("at least one incoming message must be defined", RoomPackage.Literals.PROTOCOL_CLASS__INCOMING_MESSAGES); |
| if (!roomHelpers.getAllMessages(pc, false).isEmpty()) |
| error("data driven protocols must have no outgoing messages", RoomPackage.Literals.PROTOCOL_CLASS__OUTGOING_MESSAGES); |
| break; |
| case EVENT_DRIVEN: |
| if (pc.getBase()!=null && pc.getBase().getCommType()!=CommunicationType.EVENT_DRIVEN) |
| error("super protocol has to have same communication type", RoomPackage.Literals.PROTOCOL_CLASS__COMM_TYPE); |
| if (roomHelpers.getAllMessages(pc, true).isEmpty() |
| && roomHelpers.getAllMessages(pc, false).isEmpty() |
| && roomHelpers.getAllOperations(pc, true).isEmpty() |
| && roomHelpers.getAllOperations(pc, false).isEmpty()) |
| error("at least one message/port operation (incoming or outgoing) must be defined", RoomPackage.Literals.PROTOCOL_CLASS__INCOMING_MESSAGES); |
| break; |
| case SYNCHRONOUS: |
| error("synchronous communication type not supported yet", RoomPackage.Literals.PROTOCOL_CLASS__COMM_TYPE); |
| break; |
| default: |
| } |
| |
| checkDuplicates(pc, true); |
| checkDuplicates(pc, false); |
| |
| if (pc.getBase()!=null) { |
| // derived protocol |
| if (pc.getIncomingMessages().size()>0 && pc.getOutgoingMessages().size()>0) { |
| warning("a derived protocol should add either incoming or outgoing messages, not both", RoomPackage.Literals.PROTOCOL_CLASS__OUTGOING_MESSAGES); |
| } |
| } |
| } |
| |
| /** |
| * checks duplicates in the set of messages and operations including inherited items |
| * per direction |
| * |
| * @param pc the protocol class |
| * @param incoming a flag giving the direction to be checked |
| */ |
| private void checkDuplicates(ProtocolClass pc, boolean incoming) { |
| NamedObjectList all = new NamedObjectList(); |
| all.addAll(roomHelpers.getAllMessages(pc, incoming), RoomPackage.Literals.MESSAGE__NAME); |
| all.addAll(roomHelpers.getAllOperations(pc, incoming), RoomPackage.Literals.OPERATION__NAME); |
| Iterable<NamedObject> duplicates = ValidationHelpers.inSameResource(ValidationHelpers.removeUniques(all), pc.eResource()); |
| for (NamedObject dupl : duplicates) { |
| error("duplicate message name", dupl.getObj(), dupl.getFeature()); |
| } |
| } |
| |
| /** |
| * {@link #checkInheritedNames(DataClass)} |
| */ |
| @Check |
| public void checkInheritedNames(ActorClass ac){ |
| final Set<? extends EStructuralFeature> overrideFeatures = Sets.newHashSet( |
| RoomPackage.Literals.ACTOR_CLASS__OPERATIONS, RoomPackage.Literals.ACTOR_CLASS__STRUCTORS, FSMPackage.Literals.MODEL_COMPONENT__STATE_MACHINE); |
| |
| final Map<String, EObject> superNames = Maps.newHashMap(); |
| // gather all named elements of super classes |
| ValidationHelpers.saveRecursiveVisitor(ac.getActorBase(), new Function<ActorClass, ActorClass>(){ |
| |
| @Override |
| public ActorClass apply(ActorClass input) { |
| for(EObject containee : input.eContents()){ |
| QualifiedName qualifiedName = fqnProvider.apply(containee); |
| String name = (qualifiedName != null)?qualifiedName.getLastSegment():null; |
| if(name != null && !superNames.containsKey(name)) |
| superNames.put(name, containee); |
| } |
| return input.getActorBase(); |
| }}); |
| |
| for(EObject containee : ac.eContents()){ |
| QualifiedName qualifiedName = fqnProvider.apply(containee); |
| String name = (qualifiedName != null)?qualifiedName.getLastSegment():null; |
| if(name == null) |
| continue; |
| EObject existing = superNames.get(name); |
| if(existing == null) |
| continue; |
| if(overrideFeatures.contains(containee.eContainingFeature()) && containee.eContainingFeature() == existing.eContainingFeature()) |
| continue; |
| if(superNames.containsKey(fqnProvider.apply(existing).getLastSegment())) |
| issueInheritedNameError(containee, existing); |
| } |
| } |
| |
| |
| /** |
| * Copy&Paste of {@link #checkInheritedNames(ActorClass)} |
| */ |
| @Check |
| public void checkInheritedNames(DataClass dc){ |
| final Set<? extends EStructuralFeature> overrideFeatures = Sets.newHashSet( |
| RoomPackage.Literals.DATA_CLASS__OPERATIONS, RoomPackage.Literals.DATA_CLASS__STRUCTORS); |
| |
| final Map<String, EObject> superNames = Maps.newHashMap(); |
| // gather all named elements of super classes |
| ValidationHelpers.saveRecursiveVisitor(dc.getBase(), new Function<DataClass, DataClass>(){ |
| |
| @Override |
| public DataClass apply(DataClass input) { |
| for(EObject containee : input.eContents()){ |
| QualifiedName qualifiedName = fqnProvider.apply(containee); |
| String name = (qualifiedName != null)?qualifiedName.getLastSegment():null; |
| if(name != null && !superNames.containsKey(name)) |
| superNames.put(name, containee); |
| } |
| return input.getBase(); |
| }}); |
| |
| for(EObject containee : dc.eContents()){ |
| QualifiedName qualifiedName = fqnProvider.apply(containee); |
| String name = (qualifiedName != null)?qualifiedName.getLastSegment():null; |
| if(name == null) |
| continue; |
| EObject existing = superNames.get(name); |
| if(existing == null) |
| continue; |
| if(overrideFeatures.contains(containee.eContainingFeature()) && containee.eContainingFeature() == existing.eContainingFeature()) |
| continue; |
| if(superNames.containsKey(fqnProvider.apply(existing).getLastSegment())) |
| issueInheritedNameError(containee, existing); |
| } |
| } |
| |
| /** |
| * Assuming target and source have the feature 'name', name not null, contained in a {@link RoomClass} |
| */ |
| private void issueInheritedNameError(EObject target, EObject source){ |
| String targetName = fqnProvider.apply(target).getLastSegment(); |
| String sourceName = fqnProvider.apply(source).getLastSegment(); |
| String sourceType = source.eClass().getName(); |
| String sourceOwner = roomHelpers.getRoomClass(source).getName(); |
| error("Name '" + targetName + "' is already assigned to " + sourceType + " " + sourceOwner+"."+sourceName, target, target.eClass().getEStructuralFeature("name")); |
| } |
| |
| @Check |
| public void checkMessage(Message msg) { |
| ProtocolClass pc = (ProtocolClass) msg.eContainer(); |
| if (pc.getCommType()==CommunicationType.DATA_DRIVEN) |
| if (msg.getData()==null) |
| error("Messages of data driven protocols must carry data", RoomPackage.Literals.MESSAGE__DATA); |
| } |
| |
| @Check |
| public void checkMessageFromIf(MessageFromIf mfi){ |
| if (mfi.getFrom() != null){ |
| ProtocolClass protocol = roomHelpers.getRoomProtocol((InterfaceItem)mfi.getFrom()); |
| if (protocol!=null && !protocol.eIsProxy()) { |
| if (protocol.getCommType() != CommunicationType.EVENT_DRIVEN) |
| error("port must have event driven protocol", mfi, FSMPackage.eINSTANCE.getMessageFromIf_From()); |
| } |
| } |
| } |
| |
| @Check |
| public void checkDataClass(DataClass dc) { |
| if (dc.getAttributes().isEmpty() && dc.getBase() == null) { |
| error("Non-derived data classes have to define at least one attribute", RoomPackage.Literals.DATA_CLASS__ATTRIBUTES); |
| } |
| dc.getStructors().stream().filter((op) -> op.isConstructor()).forEach((dtor) -> { |
| warning("Not implemented for C generation", dtor, null); |
| }); |
| dc.getStructors().stream().filter((op) -> !op.isConstructor()).forEach((dtor) -> { |
| error("DataClass cannot have a destructor", dtor, null); |
| }); |
| } |
| |
| @Check |
| public void checkReplicatedPortBindingPatterns(StructureClass sc) { |
| HashSet<String> haveReplPeer = new HashSet<String>(); |
| for (Binding bind : sc.getBindings()) { |
| // don't validate if unresolved, this is already be an error |
| if(EcoreUtil.ExternalCrossReferencer.find(bind).keySet().stream().anyMatch(eObj -> eObj.eIsProxy())) |
| continue; |
| |
| Port p1 = bind.getEndpoint1().getPort(); |
| Port p2 = bind.getEndpoint2().getPort(); |
| if (p1.getMultiplicity()<0 && p2.getMultiplicity()<0) { |
| if (!haveReplPeer.add(getEndpointKey(bind.getEndpoint1()))) |
| error(A_REPLICATED_PORT_MUST_HAVE_AT_MOST_ONE_REPLICATED_PEER, |
| bind, |
| RoomPackage.Literals.BINDING__ENDPOINT1); |
| if (!haveReplPeer.add(getEndpointKey(bind.getEndpoint2()))) |
| error(A_REPLICATED_PORT_MUST_HAVE_AT_MOST_ONE_REPLICATED_PEER, |
| bind, |
| RoomPackage.Literals.BINDING__ENDPOINT2); |
| } |
| } |
| } |
| |
| private String getEndpointKey(BindingEndPoint ep) { |
| String ref = ep.getActorRef()==null? "" : ep.getActorRef().getName(); |
| return ref + "#" + ep.getPort().getName(); |
| } |
| |
| @Check |
| public void checkPortClassContents(PortClass pc) { |
| if (pc.getAttributes().isEmpty() && pc.getMsgHandlers().isEmpty() && pc.getOperations().isEmpty()) |
| error("port class must not be empty", pc, RoomPackage.Literals.PORT_CLASS__ATTRIBUTES); |
| } |
| |
| @Check |
| public void checkCompoundProtocolClass(CompoundProtocolClass cpc) { |
| if (cpc.getSubProtocols().isEmpty()) |
| error("no sub protocols defined", cpc, RoomPackage.Literals.COMPOUND_PROTOCOL_CLASS__SUB_PROTOCOLS); |
| } |
| |
| @Check |
| public void checkAnnotationTarget(Annotation a) { |
| if(a.getType() == null || a.getType().eIsProxy()) |
| return; |
| |
| EObject parent = a.eContainer(); |
| EList<String> targetList = a.getType().getTargets(); |
| RoomAnnotationTargetEnum invalidTargetType = null; |
| if(parent instanceof ActorClass) { |
| ActorClass actorParent = (ActorClass)parent; |
| if(actorParent.getAnnotations().contains(a) && !targetList.contains(RoomAnnotationTargetEnum.ACTOR_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.ACTOR_CLASS; |
| } |
| else if(actorParent.getBehaviorAnnotations().contains(a) && !targetList.contains(RoomAnnotationTargetEnum.ACTOR_BEHAVIOR.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.ACTOR_BEHAVIOR; |
| } |
| } |
| else if(parent instanceof DataClass && !targetList.contains(RoomAnnotationTargetEnum.DATA_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.DATA_CLASS; |
| } |
| else if(parent instanceof ProtocolClass && !targetList.contains(RoomAnnotationTargetEnum.PROTOCOL_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.PROTOCOL_CLASS; |
| } |
| else if(parent instanceof CompoundProtocolClass && !targetList.contains(RoomAnnotationTargetEnum.COMPOUND_PROTOCOL_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.COMPOUND_PROTOCOL_CLASS; |
| } |
| else if(parent instanceof LogicalSystem && !targetList.contains(RoomAnnotationTargetEnum.LOGICAL_SYSTEM_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.LOGICAL_SYSTEM_CLASS; |
| } |
| else if(parent instanceof SubSystemClass && !targetList.contains(RoomAnnotationTargetEnum.SUBSYSTEM_CLASS.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.SUBSYSTEM_CLASS; |
| } |
| else if(parent instanceof Port && !targetList.contains(RoomAnnotationTargetEnum.PORT.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.PORT; |
| } |
| else if(parent instanceof Message && !targetList.contains(RoomAnnotationTargetEnum.MESSAGE.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.MESSAGE; |
| } |
| else if(parent instanceof RoomModel && !targetList.contains(RoomAnnotationTargetEnum.ROOM_MODEL.getLiteral())) { |
| invalidTargetType = RoomAnnotationTargetEnum.ROOM_MODEL; |
| } |
| if(invalidTargetType != null) { |
| error("AnnotationType " + a.getType().getName() + " is not allowed for target " + invalidTargetType.getLiteral(), |
| a, |
| BasePackage.Literals.ANNOTATION__TYPE, |
| INVALID_ANNOTATION_TARGET, |
| a.getType().getName(), |
| a.getType().getName()+" {target = "+invalidTargetType.getLiteral()+" ...", |
| invalidTargetType.getLiteral()); |
| } |
| } |
| |
| @Check |
| public void checkTestInstanceAnnotation(Annotation annotation){ |
| if(annotation.getType() == null || !"TestInstance".equals(annotation.getType().getName())) |
| return; |
| |
| RoomClass roomClass = roomHelpers.getRoomClass(annotation); |
| if(roomClass instanceof SubSystemClass){ |
| if(((SubSystemClass)roomClass).getThreads().size() > 0) |
| error("Annotation 'TestInstance' does not allow (explicit) LogicalThreads", annotation, null); |
| } |
| } |
| |
| @Check |
| public void checkEnumeration(EnumerationType et) { |
| if (et.getPrimitiveType()!=null) { |
| if (et.getPrimitiveType().getType()!=LiteralType.INT) { |
| error("enumerations must be of integer type", RoomPackage.Literals.ENUMERATION_TYPE__PRIMITIVE_TYPE); |
| } |
| } |
| |
| if (et.getLiterals().isEmpty()) |
| error("at least one literal has to be specified", RoomPackage.Literals.ENUMERATION_TYPE__LITERALS); |
| } |
| |
| |
| private void error(Result result) { |
| error(result.getMsg(), result.getSource(), result.getFeature(), result.getIndex()); |
| } |
| |
| @Check |
| public void checkOperations(ActorClass ac){ |
| if(roomHelpers.isCircularClassHierarchy(ac)) |
| return; |
| |
| // issue warning for deprecated ctor/dtor operations |
| for(Operation op : ac.getOperations()) |
| if(ac.getName().equals(op.getName())) |
| warning("Operation name is discouraged, may be mistaken for ctor/dtor", op, RoomPackage.Literals.OPERATION__NAME); |
| checkOperationsOverride(roomHelpers.getAllOperations(ac), ac.getOperations()); |
| } |
| |
| @Check |
| public void checkOperations(DataClass dc){ |
| if(roomHelpers.isCircularClassHierarchy(dc)) |
| return; |
| |
| // issue warning for deprecated ctor/dtor operations |
| for(Operation op : dc.getOperations()) |
| if(dc.getName().equals(op.getName())) |
| warning("Operation name is discouraged, may be mistaken for ctor/dtor", op, RoomPackage.Literals.OPERATION__NAME); |
| checkOperationsOverride(roomHelpers.getAllOperations(dc), dc.getOperations()); |
| } |
| |
| /** |
| * @param allOperations list of all operations ordered by base class first |
| * @param toCheck |
| */ |
| private void checkOperationsOverride(List<StandardOperation> allOperations, List<StandardOperation> toCheck){ |
| Map<String, StandardOperation> map = Maps.newHashMap(); |
| for(StandardOperation op : allOperations){ |
| if(!map.containsKey(op.getName())) |
| map.put(op.getName(), op); |
| } |
| for(StandardOperation op : toCheck){ |
| if(op.getName() == null) |
| continue; |
| StandardOperation baseOp = map.get(op.getName()); |
| if(baseOp == op){ |
| if(op.isOverride()) |
| error("Operation '"+op.getName()+"' must override an operation in super class", op, RoomPackage.Literals.STANDARD_OPERATION__OVERRIDE, OPERATION_EXTRANEOUS_OVERRIDE); |
| continue; |
| } |
| String baseOpFQN = roomHelpers.getRoomClass(baseOp).getName()+"."+baseOp.getName()+"()"; |
| if(!op.isOverride()){ |
| error("Implicit override of operation "+baseOpFQN, op, RoomPackage.Literals.OPERATION__NAME, OPERATION_MISSING_OVERRIDE); |
| continue; |
| } |
| if(!roomHelpers.matchingArguments(baseOp, op)) |
| error("Arguments must be identical to overriden operation in " +baseOpFQN, op, RoomPackage.Literals.OPERATION__ARGUMENTS); |
| |
| if(!roomHelpers.matchingReturnType(baseOp, op)) |
| error("Return type must be identical to overriden operation " +baseOpFQN, op, RoomPackage.Literals.OPERATION__RETURN_TYPE); |
| } |
| } |
| |
| @Check |
| public void checkMessageData(MessageData m) { |
| if (m.getDeprecatedName()!=null) { |
| warning("The data name of messages is deprecated (always named 'transitionData' in action code)", RoomPackage.Literals.MESSAGE_DATA__DEPRECATED_NAME, DEPRECATED_MESSAGE_DATA_NAME); |
| } |
| } |
| |
| /* Validate cross references for "Deprecated" annotation. Check on resource save. */ |
| @Check(CheckType.NORMAL) |
| public void checkDeprecatedRefs(RoomModel container) { |
| final Map<EObject, Annotation> deprecatedCandidates = Maps.newHashMap(); // cache instances of annotatable elements |
| Function<EObject, Annotation> findDeprecatedAnnotation = (eObj) -> { |
| if(eObj instanceof RoomElement && roomHelpers.canHaveAnnotations((RoomElement) eObj)) { |
| if(!deprecatedCandidates.containsKey(eObj)) { |
| Annotation annotation = roomHelpers.findDeprecatedAnnotation((RoomElement) eObj); |
| deprecatedCandidates.put(eObj, annotation); |
| } |
| } |
| return deprecatedCandidates.get(eObj); |
| }; |
| Predicate<Annotation> isError = (anno) -> { |
| return anno.getAttributes().stream().anyMatch(keyValue -> "error".equals(keyValue.getKey()) && ((BooleanLiteral)keyValue.getValue()).isIsTrue()); |
| }; |
| // traverse all cross references of model |
| Map<EObject, Collection<Setting>> intCrossRefs = EcoreUtil.CrossReferencer.find(container.eContents()); |
| Map<EObject, Collection<Setting>> extCrossRef = EcoreUtil.ExternalCrossReferencer.find(container); |
| FluentIterable.concat(intCrossRefs.entrySet(), extCrossRef.entrySet()).forEach(entry -> { |
| Annotation annotation = findDeprecatedAnnotation.apply(entry.getKey()); |
| if(annotation != null) { |
| if(isError.apply(annotation)) { |
| entry.getValue().forEach(setting -> error("Deprecated - this element cannot be used anymore", setting.getEObject(), setting.getEStructuralFeature())); |
| } else { |
| entry.getValue().forEach(setting -> warning("Deprecated", setting.getEObject(), setting.getEStructuralFeature())); |
| } |
| } |
| }); |
| } |
| |
| } |