| /******************************************************************************* |
| * 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) |
| * Eyrak Paen |
| * |
| *******************************************************************************/ |
| |
| |
| package org.eclipse.etrice.core.validation; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.etrice.core.common.base.Annotation; |
| import org.eclipse.etrice.core.common.base.BasePackage; |
| import org.eclipse.etrice.core.common.base.Import; |
| import org.eclipse.etrice.core.common.base.LiteralType; |
| 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.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.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.RoomAnnotationTargetEnum; |
| import org.eclipse.etrice.core.room.RoomClass; |
| 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.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.resource.IResourceDescription; |
| import org.eclipse.xtext.resource.IResourceServiceProvider; |
| import org.eclipse.xtext.scoping.impl.ImportUriResolver; |
| import org.eclipse.xtext.validation.Check; |
| |
| import com.google.common.base.Function; |
| 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; |
| |
| /* 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"; |
| |
| /* tags for quick fixes */ |
| 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"; |
| |
| @Inject ImportUriResolver importUriResolver; |
| |
| @Check |
| public void checkImportedNamespace(Import imp) { |
| if (imp.getImportURI()==null) |
| return; |
| |
| IQualifiedNameConverter nameConverter = new IQualifiedNameConverter.DefaultImpl(); |
| QualifiedName importedFQN; |
| boolean isWildcard = false; |
| try { |
| QualifiedName orig = nameConverter.toQualifiedName(imp.getImportedNamespace()); |
| isWildcard = orig.getLastSegment().equals("*"); |
| importedFQN = (isWildcard) ? orig.skipLast(1) : orig; |
| } catch(IllegalArgumentException e){ |
| return; |
| } |
| |
| String uriString = importUriResolver.resolve(imp); |
| if(uriString == null) { |
| warning("could not load referenced model", BasePackage.Literals.IMPORT__IMPORT_URI); |
| return; |
| } |
| |
| URI uri = URI.createURI(uriString); |
| ResourceSet rs = imp.eResource().getResourceSet(); |
| |
| RoomModel importedModel; |
| try { |
| Resource res = rs.getResource(uri, true); |
| if (res==null) |
| return; |
| |
| if (res.getContents().isEmpty()) { |
| error("referenced model is empty", BasePackage.Literals.IMPORT__IMPORT_URI); |
| return; |
| } |
| |
| if (!(res.getContents().get(0) instanceof RoomModel)) { |
| if (uri.lastSegment().endsWith(".room")) |
| error("referenced model is no ROOM model (but has .room extension)", BasePackage.Literals.IMPORT__IMPORT_URI); |
| else |
| warning("referenced model is no ROOM model", BasePackage.Literals.IMPORT__IMPORT_URI); |
| return; |
| } |
| importedModel = (RoomModel) res.getContents().get(0); |
| } |
| catch (RuntimeException re) { |
| warning("could not load referenced model", BasePackage.Literals.IMPORT__IMPORT_URI); |
| return; |
| } |
| |
| final IResourceServiceProvider resourceServiceProvider = IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(uri); |
| if (resourceServiceProvider==null) |
| return; |
| IResourceDescription.Manager manager = resourceServiceProvider.getResourceDescriptionManager(); |
| IResourceDescription description = manager.getResourceDescription(importedModel.eResource()); |
| |
| // TODO check for empty namespace |
| boolean exportedNameMatch = false; |
| if (description != null) { |
| Iterator<IEObjectDescription> iter = description.getExportedObjects().iterator(); |
| while(iter.hasNext()){ |
| QualifiedName exportedFQN = iter.next().getQualifiedName(); |
| if(exportedNameMatch |= importedFQN.equals(exportedFQN)) |
| break; |
| } |
| } |
| if(!exportedNameMatch) |
| error("no match for imported namespace", BasePackage.Literals.IMPORT__IMPORTED_NAMESPACE, WRONG_NAMESPACE, importedModel.getName()+".*"); |
| |
| |
| } |
| |
| @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(); |
| |
| 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("data_driven attribute not consistent in inheritance hierarchy", FSMPackage.eINSTANCE.getModelComponent_CommType()); |
| } |
| } |
| |
| @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) { |
| Result result = validationUtil.isValid(bind); |
| if (!result.isOk()) { |
| EObject sc = bind.eContainer(); |
| @SuppressWarnings("unchecked") |
| int idx = ((List<EObject>)sc.eGet(bind.eContainingFeature())).indexOf(bind); |
| error(result.getMsg(), sc, bind.eContainingFeature(), idx); |
| } |
| } |
| |
| @Check |
| public void checkServiceCompatibility(LayerConnection conn) { |
| 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.getProtocol(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){ |
| if(roomHelpers.getProtocol((InterfaceItem)mfi.getFrom()).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); |
| } |
| |
| @Check |
| public void checkReplicatedPortBindingPatterns(StructureClass sc) { |
| HashSet<Port> haveReplPeer = new HashSet<Port>(); |
| for (Binding bind : sc.getBindings()) { |
| Port p1 = bind.getEndpoint1().getPort(); |
| Port p2 = bind.getEndpoint2().getPort(); |
| if (p1.getMultiplicity()!=1 && p2.getMultiplicity()!=1) { |
| if (!haveReplPeer.add(p1)) |
| error(A_REPLICATED_PORT_MUST_HAVE_AT_MOST_ONE_REPLICATED_PEER, |
| bind, |
| RoomPackage.Literals.BINDING__ENDPOINT1); |
| if (!haveReplPeer.add(p2)) |
| error(A_REPLICATED_PORT_MUST_HAVE_AT_MOST_ONE_REPLICATED_PEER, |
| bind, |
| RoomPackage.Literals.BINDING__ENDPOINT2); |
| } |
| } |
| } |
| |
| @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; |
| } |
| 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); |
| } |
| } |
| } |