diff --git a/gradle.properties b/gradle.properties index cda01f97..4cf7d37a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,4 +25,4 @@ assertj_version = 3.21.0 junit_version = 5.8.2 # Version of published artifacts -version = 7.8.2 +version = 7.8.3 diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java new file mode 100644 index 00000000..49e05c85 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/ParentComponentInputConnectionDirectionCoCo.java @@ -0,0 +1,197 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTModifier; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLTyping; +import de.monticore.lang.sysmlparts._ast.ASTAttributeUsage; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.lang.sysmlparts._symboltable.PartDefSymbol; +import de.monticore.lang.sysmlparts._symboltable.PartUsageSymbol; +import de.monticore.lang.sysmlparts._symboltable.PortUsageSymbol; +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * ParentComponentInputConnectionDirectionCoCo + * Inputs von Oberkomponenten können nur zu Inputs von Subkomponenten oder Outputs der Oberkomponenten verbunden werden + * (alternative Formulierung): + * Inputs von Oberkomponenten können nicht zu Outputs von Subkomponenten verbunden werden. + */ +public class ParentComponentInputConnectionDirectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + // Skip validation if endpoints are missing + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; + } + + ASTEndpoint src = node.getSrc(); + ASTEndpoint tgt = node.getTgt(); + + String srcQName = endpointQName(src); + String tgtQName = endpointQName(tgt); + + ISysMLPartsScope scope = node.getEnclosingScope(); + + // Determine if endpoints reference subcomponents + boolean tgtIsSub = isSubcomponentEndpoint(tgtQName); + boolean srcIsSub = isSubcomponentEndpoint(srcQName); + + // Resolve port symbols + PortUsageSymbol tgtPort = resolvePortSymbol(scope, tgtQName, tgtIsSub); + PortUsageSymbol srcPort = resolvePortSymbol(scope, srcQName, srcIsSub); + + // If any port is unresolvable, let other CoCos handle it + if (srcPort == null || tgtPort == null) { + return; + } + + // Classify ports + boolean tgtIsInput = portIsInput(tgtPort); + boolean tgtIsOutput = portIsOutput(tgtPort); + + // Allowed connections: + // 1. Subcomponent with Input ports + // 2. Parent component with Output ports + boolean allowed = (tgtIsSub && tgtIsInput) || (!tgtIsSub && tgtIsOutput); + + if(srcIsSub || !portIsInput(srcPort)){ + // Source is neither input nor ParentComponent + // CoCo does not apply + return; + } + if( + (portIsInOutput(srcPort)) || + (portIsInOutput(tgtPort)) + ) { + Log.warn("0x10AA6 Warning: Connection involves an 'inout' port which may have ambiguous directionality.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd()); + } + + if (!allowed) { + Log.error( + "0x10AA6 Illegal connection: inputs of parent components can only be " + + "connected to inputs of subcomponents or outputs of the parent component.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + // -------- Hilfsmethoden -------- + + /** Qualified Name des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + if (ep.getMCQualifiedName() != null) { + return ep.getMCQualifiedName().toString(); + } + return ""; + } + + /** + * Heuristik: ein Name mit '.' steht für einen Port einer Subkomponente, + * z.B. "a.out". Ohne Punkt = Port der Oberkomponente. + */ + protected boolean isSubcomponentEndpoint(String qname) { + return qname.contains("."); + } + + /** Resolve port symbol based on whether it's in a subcomponent */ + protected PortUsageSymbol resolvePortSymbol(ISysMLPartsScope scope, + String qname, boolean isSub) { + if (isSub) { + return resolvePortOfSubPart(scope, qname); + } else { + return resolvePort(scope, qname); + } + } + + /** Resolve PortUsageSymbol by qualified name */ + protected PortUsageSymbol resolvePort(ISysMLPartsScope scope, String qname) { + List result = scope.resolvePortUsageSubKinds( + true, // search in enclosing scopes + qname, // qualified name, e.g., "a.out" + AccessModifier.ALL_INCLUSION, // ignore visibility + p -> true // no additional filtering + ); + + if (result.isEmpty()) { + return scope.resolvePortUsage(qname).orElse(null); + } + // If multiple candidates, take the first (should be unique in your models) + return result.get(0); + } + + /** Resolve port within a subcomponent */ + protected PortUsageSymbol resolvePortOfSubPart(ISysMLPartsScope scope, + String qname) { + // Split "a.out" into part "a" and port "out" + int lastDot = qname.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } + + String partName = qname.substring(0, lastDot); + String portName = qname.substring(lastDot + 1); + + // Resolve the subcomponent + Optional partSymbol = scope.resolvePartUsage(partName); + if (partSymbol.isEmpty()) { + return null; + } + + Optional partDef = partSymbol.get().getPartDef(); + if (partDef.isPresent()) { + ISysMLPartsScope partScope = partDef.get().getSpannedScope(); + return resolvePort(partScope, portName); + } + + return null; + } + + /** Extract modifiers from PortUsageSymbol */ + protected ASTModifier getModifiersFromPortUsageSymbol(PortUsageSymbol symbol) { + ASTAttributeUsage portAttributeUsageAST = (ASTAttributeUsage) + symbol.getAstNode() + .getPortDefs() + .get(0) + .getSysMLElementList() + .get(0); + return portAttributeUsageAST.getModifier(); + } + + protected boolean portIsInput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsInAndNotConjugated = mods.isIn() && !portIsConjugated(symbol); + boolean portIsOutAndConjugated = mods.isOut() && portIsConjugated(symbol); + return (portIsInAndNotConjugated || portIsOutAndConjugated); + } + + protected boolean portIsOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsOutAndNotConjugated = mods.isOut() && !portIsConjugated(symbol); + boolean portIsInAndConjugated = mods.isIn() && portIsConjugated(symbol); + return (portIsOutAndNotConjugated || portIsInAndConjugated); + } + + protected boolean portIsInOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + return mods.isInout(); + } + + protected boolean portIsConjugated(PortUsageSymbol symbol) { + return + ((ASTSysMLTyping) symbol + .getAstNode() + .getSpecialization(0)) + .isConjugated(); + } +} diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java new file mode 100644 index 00000000..faced490 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/PartTypeDefinitionExistsCoCo.java @@ -0,0 +1,39 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTSpecialization; +import de.monticore.lang.sysmlparts._ast.ASTPartUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartUsageCoCo; +import de.monticore.types.mcbasictypes._ast.ASTMCType; +import de.se_rwth.commons.logging.Log; + +import java.util.stream.Collectors; + +/** + * CoCo1: Jeder in "part name:Typ" verwendete Typ muss eine existierende Part-Definition sein. + */ +public class PartTypeDefinitionExistsCoCo implements SysMLPartsASTPartUsageCoCo { + + protected String printPartType(ASTMCType type) { + return type.printType(); + } + + @Override + public void check(ASTPartUsage node) { + var nonExistent = node.streamSpecializations() + .flatMap(ASTSpecialization::streamSuperTypes) + .filter(t -> + node.getEnclosingScope().resolvePartDef(printPartType(t)).isEmpty() + ) + .collect(Collectors.toList()); + + for (var problem : nonExistent) { + Log.error( + "0x10AA1 The type referenced in a PartUsage \"" + printPartType(problem) + + "\" does not exist as a part definition.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } +} diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java new file mode 100644 index 00000000..f63d428e --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/QualifiedPortNameExistsCoCo.java @@ -0,0 +1,96 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.lang.sysmlparts._symboltable.PartDefSymbol; +import de.monticore.lang.sysmlparts._symboltable.PartUsageSymbol; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * QualifiedPortNameExistsCoCo: + * In einer Verbindung "connect a.b to c.d" muss jeder verwendete (qualifizierte) Portname existieren. + */ +public class QualifiedPortNameExistsCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; // keine Verbindung + } + + ISysMLPartsScope scope = node.getEnclosingScope(); + + checkEndpoint(scope, node.getSrc(), node); + checkEndpoint(scope, node.getTgt(), node); + } + + protected void checkEndpoint(ISysMLPartsScope scope, + ASTEndpoint endpoint, + ASTConnectionUsage conn) { + String qname = endpointQName(endpoint); + if (qname.isEmpty()) { + return; + } + + String[] parts = qname.split("\\."); + + // Qualifizierter Name: a.b -> resolve a (PartUsage), dann b in PartDef + if (parts.length >= 2) { + String partName = parts[0]; + String portName = parts[parts.length - 1]; + + Optional partOpt = scope.resolvePartUsageLocally(partName); + + if (partOpt.isEmpty()) { + // Existenz der Subkomponente wird bereits in MKPX_CoCo3 geprüft. + return; + } + + var partDefOpt = partOpt.get().getPartDef(); + if (partDefOpt.isEmpty()) { + // CoCo3 + return; + } + + PartDefSymbol partDef = partDefOpt.get(); + boolean portExistsInDef = partDef.getSpannedScope() + .resolvePortUsageLocallyMany(false, portName, AccessModifier.ALL_INCLUSION, p -> true) + .size() == 1; + + if (!portExistsInDef) { + Log.error( + "0x10AA4 The port '" + portName + "' does not exist in the definition of subcomponent '" + + partName + "'.", + conn.get_SourcePositionStart(), + conn.get_SourcePositionEnd() + ); + } + } + else { + // Unqualifiziert: Port der Oberkomponente muss lokal existieren + String portName = parts[0]; + List localPorts = scope.resolvePortUsageLocallyMany( + false, portName, AccessModifier.ALL_INCLUSION, p -> true); + if (localPorts.isEmpty()) { + Log.error( + "0x10AA4 The port used in 'connect' '" + portName + + "' does not exist in the parent component.", + conn.get_SourcePositionStart(), + conn.get_SourcePositionEnd() + ); + } + } + } + + /** Liefert den qualifizierten Namen des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + return ep.getMCQualifiedName() != null ? ep.getMCQualifiedName().toString() : ""; + } +} diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java new file mode 100644 index 00000000..3b7338d3 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/RefinementTargetDefinitionExistsCoCo.java @@ -0,0 +1,41 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTSpecialization; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLRefinement; +import de.monticore.lang.sysmlparts._ast.ASTPartDef; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTPartDefCoCo; +import de.monticore.types.mcbasictypes._ast.ASTMCType; +import de.se_rwth.commons.logging.Log; + +import java.util.stream.Collectors; + +/** + * CoCo2: Jeder im "part def X refines Name" verwendete Name muss eine existierende Part-Definition sein. + */ +public class RefinementTargetDefinitionExistsCoCo implements SysMLPartsASTPartDefCoCo { + + protected String printPartType(ASTMCType type) { + return type.printType(); + } + + @Override + public void check(ASTPartDef node) { + var nonExistent = node.streamSpecializations() + .filter(s -> s instanceof ASTSysMLRefinement) + .flatMap(ASTSpecialization::streamSuperTypes) + .filter(t -> + node.getEnclosingScope().resolvePartDef(printPartType(t)).isEmpty() + ) + .collect(Collectors.toList()); + + for (var problem : nonExistent) { + Log.error( + "0x10AA2 The name used in 'refines' \"" + printPartType(problem) + + "\" does not exist as a part definition.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } +} diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java new file mode 100644 index 00000000..8a3389c3 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/SubcomponentOutputConnectionDirectionCoCo.java @@ -0,0 +1,194 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTModifier; +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlbasis._ast.ASTSysMLTyping; +import de.monticore.lang.sysmlparts._ast.ASTAttributeUsage; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.*; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +import java.util.List; +import java.util.Optional; + +/** + * SubcomponentOutputConnectionDirectionCoCo + * Checks that outputs of subcomponents can only be connected to: + * 1. Inputs of subcomponents, or + * 2. Outputs of the parent component. + */ +public class SubcomponentOutputConnectionDirectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + // Skip validation if endpoints are missing + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; + } + + ASTEndpoint src = node.getSrc(); + ASTEndpoint tgt = node.getTgt(); + + String srcQName = endpointQName(src); + String tgtQName = endpointQName(tgt); + + ISysMLPartsScope scope = node.getEnclosingScope(); + + // Determine if endpoints reference subcomponents + boolean tgtIsSub = isSubcomponentEndpoint(tgtQName); + boolean srcIsSub = isSubcomponentEndpoint(srcQName); + + // Resolve port symbols + PortUsageSymbol tgtPort = resolvePortSymbol(scope, tgtQName, tgtIsSub); + PortUsageSymbol srcPort = resolvePortSymbol(scope, srcQName, srcIsSub); + + // If any port is unresolvable, let other CoCos handle it + if (srcPort == null || tgtPort == null) { + return; + } + + // Classify ports + boolean tgtIsInput = portIsInput(tgtPort); + boolean tgtIsOutput = portIsOutput(tgtPort); + + // Allowed connections: + // 1. Subcomponent with Input ports + // 2. Parent component with Output ports + boolean allowed = (tgtIsSub && tgtIsInput) || (!tgtIsSub && tgtIsOutput); + + if(!srcIsSub || !portIsOutput(srcPort)){ + // Source is neither output nor Subcomponent + // CoCo does not apply + return; + } + if( + (portIsInOutput(srcPort)) || + (portIsInOutput(tgtPort)) + ) { + Log.warn("0x10AA5 Warning: Connection involves an 'inout' port which may have ambiguous directionality.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd()); + } + + if (!allowed) { + Log.error( + "0x10AA5 Illegal connection: outputs of subcomponents can only be " + + "connected to inputs of subcomponents or outputs of the parent component.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + // -------- Hilfsmethoden -------- + + /** Qualified Name des Endpunkts als String. */ + protected String endpointQName(ASTEndpoint ep) { + if (ep.getMCQualifiedName() != null) { + return ep.getMCQualifiedName().toString(); + } + return ""; + } + + /** + * Heuristik: ein Name mit '.' steht für einen Port einer Subkomponente, + * z.B. "a.out". Ohne Punkt = Port der Oberkomponente. + */ + protected boolean isSubcomponentEndpoint(String qname) { + return qname.contains("."); + } + + /** Resolve port symbol based on whether it's in a subcomponent */ + protected PortUsageSymbol resolvePortSymbol(ISysMLPartsScope scope, + String qname, boolean isSub) { + if (isSub) { + return resolvePortOfSubPart(scope, qname); + } else { + return resolvePort(scope, qname); + } + } + + /** Resolve PortUsageSymbol by qualified name */ + protected PortUsageSymbol resolvePort(ISysMLPartsScope scope, String qname) { + List result = scope.resolvePortUsageSubKinds( + true, // search in enclosing scopes + qname, // qualified name, e.g., "a.out" + AccessModifier.ALL_INCLUSION, // ignore visibility + p -> true // no additional filtering + ); + + if (result.isEmpty()) { + return scope.resolvePortUsage(qname).orElse(null); + } + // If multiple candidates, take the first (should be unique in your models) + return result.get(0); + } + + /** Resolve port within a subcomponent */ + protected PortUsageSymbol resolvePortOfSubPart(ISysMLPartsScope scope, + String qname) { + // Split "a.out" into part "a" and port "out" + int lastDot = qname.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } + + String partName = qname.substring(0, lastDot); + String portName = qname.substring(lastDot + 1); + + // Resolve the subcomponent + Optional partSymbol = scope.resolvePartUsage(partName); + if (partSymbol.isEmpty()) { + return null; + } + + Optional partDef = partSymbol.get().getPartDef(); + if (partDef.isPresent()) { + ISysMLPartsScope partScope = partDef.get().getSpannedScope(); + return resolvePort(partScope, portName); + } + + return null; + } + + /** Extract modifiers from PortUsageSymbol */ + protected ASTModifier getModifiersFromPortUsageSymbol(PortUsageSymbol symbol) { + ASTAttributeUsage portAttributeUsageAST = (ASTAttributeUsage) + symbol.getAstNode() + .getPortDefs() + .get(0) + .getSysMLElementList() + .get(0); + return portAttributeUsageAST.getModifier(); + } + + protected boolean portIsInput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsInAndNotConjugated = mods.isIn() && !portIsConjugated(symbol); + boolean portIsOutAndConjugated = mods.isOut() && portIsConjugated(symbol); + return (portIsInAndNotConjugated || portIsOutAndConjugated); + } + + protected boolean portIsOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + boolean portIsOutAndNotConjugated = mods.isOut() && !portIsConjugated(symbol); + boolean portIsInAndConjugated = mods.isIn() && portIsConjugated(symbol); + return (portIsOutAndNotConjugated || portIsInAndConjugated); + } + + protected boolean portIsInOutput(PortUsageSymbol symbol) { + ASTModifier mods = getModifiersFromPortUsageSymbol(symbol); + return mods.isInout(); + } + + protected boolean portIsConjugated(PortUsageSymbol symbol) { + return + ((ASTSysMLTyping) symbol + .getAstNode() + .getSpecialization(0)) + .isConjugated(); + } +} diff --git a/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java new file mode 100644 index 00000000..70161fe8 --- /dev/null +++ b/language/src/main/java/de/monticore/lang/sysmlv2/cocos/UniqueSubPartNamesInConnectionCoCo.java @@ -0,0 +1,72 @@ +/* (c) https://github.com/MontiCore/monticore */ +package de.monticore.lang.sysmlv2.cocos; + +import de.monticore.lang.sysmlbasis._ast.ASTEndpoint; +import de.monticore.lang.sysmlparts._ast.ASTConnectionUsage; +import de.monticore.lang.sysmlparts._cocos.SysMLPartsASTConnectionUsageCoCo; +import de.monticore.lang.sysmlparts._symboltable.ISysMLPartsScope; +import de.monticore.symboltable.modifiers.AccessModifier; +import de.se_rwth.commons.logging.Log; + +/** + * CoCo3: Jeder in "connect a.b to c.d" verwendete Name von Subkomponenten (a und c) + * muss eindeutig im Modell vorhanden sein. + */ +public class UniqueSubPartNamesInConnectionCoCo implements SysMLPartsASTConnectionUsageCoCo { + + @Override + public void check(ASTConnectionUsage node) { + if (!node.isPresentSrc() || !node.isPresentTgt()) { + return; // keine Verbindung + } + + var scope = node.getEnclosingScope(); + + checkSubcomponentQualifier(scope, node.getSrc(), node); + checkSubcomponentQualifier(scope, node.getTgt(), node); + } + + protected void checkSubcomponentQualifier(ISysMLPartsScope scope, + ASTEndpoint endpoint, + ASTConnectionUsage node) { + String qname = endpointQName(endpoint); + String subName = extractFirstSegment(qname); + + if (subName == null) { + return; + } + + int matches = scope + .resolvePartUsageLocallyMany(false, subName, + AccessModifier.ALL_INCLUSION, p -> true) + .size(); + + if (matches > 1) { + Log.error( + "0x10AA3 The subcomponent name used in 'connect' \"" + subName + + "\" is not unique in the model.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } else if (matches != 1) { /** matches = 0 */ + Log.error( + "0x10AA3 The subcomponent name used in 'connect' \"" + subName + + "\" does not exist in the model.", + node.get_SourcePositionStart(), + node.get_SourcePositionEnd() + ); + } + } + + protected String endpointQName(ASTEndpoint ep) { + return ep.getMCQualifiedName() != null ? ep.getMCQualifiedName().toString() : ""; + } + + protected String extractFirstSegment(String qname) { + int dot = qname.indexOf('.'); + if (dot > 0) { + return qname.substring(0, dot); + } + return null; + } +} diff --git a/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java new file mode 100644 index 00000000..4374e2b7 --- /dev/null +++ b/language/src/test/java/cocos/ParentComponentInputConnectionDirectionCoCoTest.java @@ -0,0 +1,157 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.ParentComponentInputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ParentComponentInputConnectionDirectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class InputConnectionTests { + @Test + public void testValid() throws IOException { + String validModel = + "port def InPort { in attribute data: int; }" + + "port def OutPort { out attribute data: int; }" + + "part def A { port input: InPort; }" + + "part def B { port output: OutPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "port sysInAnother: InPort;" + + "port sysOut: OutPort;" + + "part a: A;" + + "part b: B;" + + "connect sysIn to a.input;" + + "connect sysInAnother to sysOut;" + + "}"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testValidConjugatedModel() throws IOException { + String validModel = + "port def InPort { in attribute data: int; }" + + "part def A { port input: InPort; }" + + "part def B { port output: ~InPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "port sysInAnother: InPort;" + + "port sysOut: ~InPort;" + + "part a: A;" + + "part b: B;" + + "connect sysIn to a.input;" + + "connect sysInAnother to sysOut;" + + "}"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "port def InPort { in attribute data: int; }" + + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "part a: A;" + + "connect sysIn to a.output;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA6"); + } + + @Test + public void testInvalidConjugatedModel() throws IOException { + String invalidModel = + "port def InPort { in attribute data: int; }" + + "part def A { port output: ~InPort; }" + + "part def System {" + + "port sysIn: InPort;" + + "part a: A;" + + "connect sysIn to a.output;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA6"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new ParentComponentInputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +} diff --git a/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java new file mode 100644 index 00000000..a5a0f380 --- /dev/null +++ b/language/src/test/java/cocos/PartTypeDefinitionExistsCoCoTest.java @@ -0,0 +1,107 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.PartTypeDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PartTypeDefinitionExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class PartTypeDefinitionExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def SubComponent1;" + + "part def SubComponent2;" + + "part def MainComponent{" + + "part subcomp1: SubComponent1;" + + "part subcomp2: SubComponent2;" + + "}"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "part def SubComponent1;" + + "part def MainComponent{" + + "part subcomp1: SubComponent1;" + + "part subcomp2: UndefinedComponent;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA1"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new PartTypeDefinitionExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +} diff --git a/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java new file mode 100644 index 00000000..b55a6e8e --- /dev/null +++ b/language/src/test/java/cocos/QualifiedPortNameExistsCoCoTest.java @@ -0,0 +1,110 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.QualifiedPortNameExistsCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QualifiedPortNameExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class QualifiedPortNameExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.p to b.q;" + + "}"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.wrongName to b.q;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA4"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new QualifiedPortNameExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +} diff --git a/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java new file mode 100644 index 00000000..a9e924de --- /dev/null +++ b/language/src/test/java/cocos/RefinementTargetDefinitionExistsCoCoTest.java @@ -0,0 +1,98 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.RefinementTargetDefinitionExistsCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RefinementTargetDefinitionExistsCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class RefinementTargetDefinitionExistsCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def BasePart;" + + "part def Refining refines BasePart;"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalid() throws IOException { + String invalidModel = "part def Refining refines UndefinedBasePart;"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA2"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new RefinementTargetDefinitionExistsCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +} diff --git a/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java new file mode 100644 index 00000000..18c74360 --- /dev/null +++ b/language/src/test/java/cocos/SubcomponentOutputConnectionDirectionCoCoTest.java @@ -0,0 +1,175 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.SubcomponentOutputConnectionDirectionCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SubcomponentOutputConnectionDirectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class OutputConnectionTests { + + @Test + public void testValid() throws IOException { + String validModel = + "port def OutPort { out attribute data: int; }" + + "port def InPort { in attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port input: InPort; }" + + "part def C { port output: OutPort; }" + + "part def System {" + + "port sysOutput: OutPort;" + + "part a: A;" + + "part b: B;" + + "part c: C;" + + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + + "}"; + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testValidConjugatedModel() throws IOException { + String validModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port input: ~OutPort; }" + + "part def C { port output: OutPort; }" + + "part def System {" + + "port sysOutput: OutPort;" + + "part a: A;" + + "part b: B;" + + "part c: C;" + + "connect a.output to b.input;" // (Sub) Output -> (Sub) Input + + "connect c.output to sysOutput;" // (Sub) Output -> (main) Output + + "}"; + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalidSubOutToSubOut() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def B { port output: OutPort; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.output to b.output;" // (Sub) Output -> (Sub) Output + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); + } + + @Test + public void testInvalidSubOutToMainInConjugatedModel() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysInput: ~OutPort;" + + "part a: A;" + + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); + } + + @Test + public void testInvalidSubOutToMainIn() throws IOException { + String invalidModel = + "port def OutPort { out attribute data: int; }" + + "port def InPort { in attribute data: int; }" + + "part def A { port output: OutPort; }" + + "part def System {" + + "port sysInput: InPort;" + + "part a: A;" + + "connect a.output to sysInput;" // (Sub) Output -> (main) Input + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA5"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new SubcomponentOutputConnectionDirectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +} diff --git a/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java new file mode 100644 index 00000000..38913d6b --- /dev/null +++ b/language/src/test/java/cocos/UniqueSubPartNamesInConnectionCoCoTest.java @@ -0,0 +1,162 @@ +/* (c) https://github.com/MontiCore/monticore */ +package cocos; + +import de.monticore.lang.sysmlv2.SysMLv2Mill; +import de.monticore.lang.sysmlv2.SysMLv2Tool; +import de.monticore.lang.sysmlv2._ast.ASTSysMLModel; +import de.monticore.lang.sysmlv2._cocos.SysMLv2CoCoChecker; +import de.monticore.lang.sysmlv2._parser.SysMLv2Parser; +import de.monticore.lang.sysmlv2._symboltable.ISysMLv2ArtifactScope; +import de.monticore.lang.sysmlv2.cocos.UniqueSubPartNamesInConnectionCoCo; +import de.se_rwth.commons.logging.Finding; +import de.se_rwth.commons.logging.Log; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UniqueSubPartNamesInConnectionCoCoTest { + + private static final String MODEL_PATH = "src/test/resources/parser"; + + private SysMLv2Parser parser = SysMLv2Mill.parser(); + + @BeforeAll + public static void init() { + Log.init(); + SysMLv2Mill.init(); + } + + @BeforeEach + public void reset() { + SysMLv2Mill.globalScope().clear(); + SysMLv2Mill.initializePrimitives(); + SysMLv2Mill.addCollectionTypes(); + Log.clearFindings(); + } + + @Nested + public class UniqueSubPartNamesInConnectionCoCoTests { + @Test + public void testValid() throws IOException { + String validModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "part b: B;" + + "connect a.p to b.q;" + + "}"; + + var ast = parse(validModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(0); + } + + @Test + public void testInvalidUndefined() throws IOException { + String invalidModel = + "part def A { port p: int; }" + + "part def B { port q: ~int; }" + + "part def System {" + + "part a: A;" + + "connect a.p to c.q;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(1); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + } + + @Test + public void testInvalidDuplicateName() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part a: A;" + + "part a: A;" + + "connect a.p to a.p;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + @Test + public void testInvalidBothUndefined() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part a: A;" + + "connect undefined1.p to undefined2.p;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + @Test + public void testInvalidUndefinedAndDuplicateName() throws IOException { + String invalidModel = + "part def A { port p; }" + + "part def System {" + + "part duplicate1: A;" + + "part duplicate1: ~A;" + + "connect duplicate1.p to undefined3.p;" + + "}"; + + var ast = parse(invalidModel); + createSt(ast); + var errors = check(ast); + assertThat(errors).hasSize(2); + assertThat(errors.get(0).getMsg()).contains("0x10AA3"); + assertThat(errors.get(1).getMsg()).contains("0x10AA3"); + } + + private ASTSysMLModel parse(String model) throws IOException { + var optAst = SysMLv2Mill.parser().parse_String(model); + assertThat(optAst).isPresent(); + return optAst.get(); + } + + private ISysMLv2ArtifactScope createSt(ASTSysMLModel ast) { + var tool = new SysMLv2Tool(); + var scope = tool.createSymbolTable(ast); + tool.completeSymbolTable(ast); + return scope; + } + + private List check(ASTSysMLModel ast) { + var checker = new SysMLv2CoCoChecker(); + checker.addCoCo(new UniqueSubPartNamesInConnectionCoCo()); + Log.enableFailQuick(false); + checker.checkAll(ast); + return Log.getFindings().stream().filter(Finding::isError).collect( + Collectors.toList()); + } + + @AfterEach + void clearLog() { + Log.clearFindings(); + Log.enableFailQuick(true); + } + } +}