Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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<PortUsageSymbol> 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<PartUsageSymbol> partSymbol = scope.resolvePartUsage(partName);
if (partSymbol.isEmpty()) {
return null;
}

Optional<PartDefSymbol> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<PartUsageSymbol> 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() : "";
}
}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
}
Loading
Loading