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
4 changes: 2 additions & 2 deletions extension/src-diagram-snippets/css/sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
gap: 6px;
padding: 6px;

width: 100%;
height: 100%;
Expand Down
2 changes: 1 addition & 1 deletion extension/src-diagram-snippets/html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const buttonID = 'snippetAddBnt';
* The actual snippets are patched into the placeholder later.
*/
export const panel: VNode = <div class-sidebar__content="true">
<h3 class-sidebar__title="true">{"Diagram Blueprints"}</h3>
<h3>{"Diagram Blueprints"}</h3>
<input type="text" id={textFieldID} />
<button type="button" id={buttonID}>Add Diagram Blueprint</button>
<div class-sidebar__panel-content="true" id="snippets"><div id={placeholderForSnippetsID}></div></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
"snippets": [
{"name": "simpleCSSnippet",
"code": "\nControlStructure\nCS {\n Controller {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControlledProcess {\n hierarchyLevel 1\n feedback {\n [fb \"feedback\"] -> Controller\n }\n }\n}\n"},
{"name": "simpleCSWithAcsSnippet",
"code": "\nControlStructure\nCS {\n Controller {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> Actuator\n }\n }\n Actuator {\n hierarchyLevel 1\n controlActions {\n [caAc \"control action\"] -> ControlledProcess\n }\n }\n Sensor {\n hierarchyLevel 1\n feedback {\n [fbS \"feedback\"] -> Controller\n }\n }\n ControlledProcess {\n hierarchyLevel 2\n feedback {\n [fb \"feedback\"] -> Sensor\n }\n }\n}\n"},
"code": "\nControlStructure\nCS {\n Controller {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControlledProcess {\n feedback {\n [fb \"feedback\"] -> Controller\n }\n }\n}\n"},
{"name": "consControllerSnippet",
"code": "\nControlStructure\nCS {\n ControllerA {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> ControllerB\n }\n }\n ControllerB {\n hierarchyLevel 1\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControlledProcess {\n hierarchyLevel 2\n feedback {\n [fb \"feedback\"] -> ControllerB\n }\n }\n}\n"},
"code": "\nControlStructure\nCS {\n ControllerA {\n controlActions {\n [ca \"control action\"] -> ControllerB\n }\n }\n ControllerB {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControlledProcess {\n feedback {\n [fb \"feedback\"] -> ControllerB\n }\n }\n}\n"},
{"name": "parallelContsSnippet",
"code": "\nControlStructure\nCS {\n ControllerA {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControllerB {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControlledProcess {\n hierarchyLevel 1\n feedback {\n [fbA \"feedback\"] -> ControllerA\n [fbB \"feedback\"] -> ControllerB\n }\n }\n}\n"},
"code": "\nControlStructure\nCS {\n ControllerA {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControllerB {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n }\n ControlledProcess {\n feedback {\n [fbA \"feedback\"] -> ControllerA\n [fbB \"feedback\"] -> ControllerB\n }\n }\n}\n"},
{"name": "consContsWithLoopSnippet",
"code": "\nControlStructure\nCS {\n ControllerA {\n hierarchyLevel 0\n controlActions {\n [caC \"control action\"] -> ControlledProcess\n [caB \"control action\"] -> ControllerB\n }\n }\n ControllerB {\n hierarchyLevel 1\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControlledProcess {\n hierarchyLevel 2\n feedback {\n [fbA \"feedback\"] -> ControllerA\n [fbB \"feedback\"] -> ControllerB\n }\n }\n}\n"},
"code": "\nControlStructure\nCS {\n ControllerA {\n controlActions {\n [caC \"control action\"] -> ControlledProcess\n [caB \"control action\"] -> ControllerB\n }\n }\n ControllerB {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControlledProcess {\n feedback {\n [fbA \"feedback\"] -> ControllerA\n [fbB \"feedback\"] -> ControllerB\n }\n }\n}\n"},
{"name": "CSWithSubComponents",
"code": "ControlStructure\n Aircraft {\n ControllerA {\n hierarchyLevel 0\n controlActions {\n [ca \"control action\"] -> ControllerB1\n }\n }\n ControllerB {\n hierarchyLevel 1\n ControllerB1 {\n controlActions {\n [ca \"control action\"] -> ControllerB2\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControllerB2 {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerB1\n }\n }\n }\n ControlledProcess {\n hierarchyLevel 2\n feedback {\n [fb \"feedback\"] -> ControllerB2\n }\n }\n }"}
"code": "ControlStructure\n Aircraft {\n ControllerA {\n controlActions {\n [ca \"control action\"] -> ControllerB1\n }\n }\n ControllerB {\n ControllerB1 {\n controlActions {\n [ca \"control action\"] -> ControllerB2\n }\n feedback {\n [fb \"feedback\"] -> ControllerA\n }\n }\n ControllerB2 {\n controlActions {\n [ca \"control action\"] -> ControlledProcess\n }\n feedback {\n [fb \"feedback\"] -> ControllerB1\n }\n }\n }\n ControlledProcess {\n feedback {\n [fb \"feedback\"] -> ControllerB2\n }\n }\n }"}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
options["org.eclipse.elk.layered.nodePlacement.networkSimplex.nodeFlexibility.default"] = "NONE";
} else if (relationshipNode) {
options["org.eclipse.elk.layered.spacing.edgeEdgeBetweenLayers"] = "6";
options["org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers"] = "13";
}

// model order is used to determine the order of the children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export class STPACompletionProvider extends DefaultCompletionProvider {
): void {
if (next.type === Node && next.property === "name") {
const generatedText = `Comp {
hierarchyLevel 0
label "Component"
processModel {
}
Expand Down
92 changes: 90 additions & 2 deletions extension/src-language-server/stpa/services/stpa-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export class StpaValidator {
checkMissingFeedback = true;

checkForConflictingUCAs = true;

// TODO: user should be able to toggle this check
/** Boolean option to toggle the check whether there is only one action to send in each context. */
checkForSingleAction = false;

/**
* Map from node ID to a list of nodes to which a feedback is missing.
Expand Down Expand Up @@ -284,6 +288,11 @@ export class StpaValidator {
}
// check for conflicts between UCAs and DCAs
this.checkForConflictsBetweenUCAsAndDCAs(model.rules, model.allDCAs, accept);

if (this.checkForSingleAction) {
// check that in each context only one action should be provided, otherwise the generated statechart sbm does not work
this.checkSingleAction(model.rules, model.allDCAs, accept);
}
}

/**
Expand Down Expand Up @@ -357,6 +366,85 @@ export class StpaValidator {
}
}

/**
* Validates that only one action is provided in each context.
* When multiple actions should be provided in the same context, the generated statechart SBM does not work.
* @param ucas The UCAs to check.
* @param dcas The DCAs to check.
* @param accept
*/
protected checkSingleAction(ucas: Rule[], dcas: DCARule[], accept: ValidationAcceptor): void {
// check the UCAs among each other
for (let i = 0; i < ucas.length; i++) {
const uca = ucas[i];
for (let j = i + 1; j < ucas.length; j++) {
const otherUca = ucas[j];
// can only violate the single action constraint if they have different control actions and state that they should be provided
const uca1Type = uca.type === UCA_TYPE.PROVIDED ? UCA_TYPE.PROVIDED : UCA_TYPE.NOT_PROVIDED;
const uca2Type = otherUca.type === UCA_TYPE.PROVIDED ? UCA_TYPE.PROVIDED : UCA_TYPE.NOT_PROVIDED;
const uca1Action = uca.system?.$refText + "." + uca.action?.$refText;
const uca2Action = otherUca.system?.$refText + "." + otherUca.action?.$refText;
if (uca1Action !== uca2Action && uca1Type === UCA_TYPE.NOT_PROVIDED && uca2Type === UCA_TYPE.NOT_PROVIDED) {
for (const context of otherUca.contexts) {
for (const otherContext of uca.contexts) {
// if they both state that the action must be sent and have same context for different control actions, they violate the constraint
if (this.isSameContext(context, otherContext)) {
accept("warning", "Violates single action constraint, see " + uca.name + " " + otherContext.name, {
node: context,
});
}
}
}
}
}
}

// check the DCAs among each other
for (let i = 0; i < dcas.length; i++) {
const dca = dcas[i];
for (let j = i + 1; j < dcas.length; j++) {
const otherDca = dcas[j];
// can only violate the single action constraint if they have different control actions and state that they should be provided
const dca1Action = dca.system?.$refText + "." + dca.action?.$refText;
const dca2Action = otherDca.system?.$refText + "." + otherDca.action?.$refText;
if (dca1Action !== dca2Action && dca.type === UCA_TYPE.PROVIDED && otherDca.type === UCA_TYPE.PROVIDED) {
for (const context of otherDca.contexts) {
for (const otherContext of dca.contexts) {
// if they both state that the action must be sent and have same context for different control actions, they violate the constraint
if (this.isSameContext(context, otherContext)) {
accept("warning", "Violates single action constraint, see " + dca.name + " " + otherContext.name, {
node: context,
});
}
}
}
}
}
}

// check the UCAs against the DCAs, as a DCA of type provided also provides an action and can therefore conflict with a UCA of type not provided
for (const dca of dcas) {
for (const uca of ucas) {
// can only violate the single action constraint if they have different control actions and state that they should be provided
const ucaType = uca.type === UCA_TYPE.PROVIDED ? UCA_TYPE.PROVIDED : UCA_TYPE.NOT_PROVIDED;
const dcaAction = dca.system?.$refText + "." + dca.action?.$refText;
const ucaAction = uca.system?.$refText + "." + uca.action?.$refText;
if (dcaAction !== ucaAction && dca.type === UCA_TYPE.PROVIDED && ucaType === UCA_TYPE.NOT_PROVIDED) {
for (const context of dca.contexts) {
for (const otherContext of uca.contexts) {
// if they both state that the action must be sent and have same context for different control actions, they violate the constraint
if (this.isSameContext(context, otherContext)) {
accept("warning", "Violates single action constraint, see " + uca.name + " " + otherContext.name, {
node: context,
});
}
}
}
}
}
}
}

/**
* Validates that there are no conflicts between UCAs and DCAs.
* A DCA is not allowed to have the same type and context as a UCA.
Expand Down Expand Up @@ -401,7 +489,7 @@ export class StpaValidator {
v => v.variable.$refText === context1.assignedValues[i].variable.$refText
);
if (
varIndex === -1 ||
varIndex !== -1 &&
context2.assignedValues[varIndex].value.$refText !== context1.assignedValues[i].value.$refText
) {
isSame = false;
Expand All @@ -416,7 +504,7 @@ export class StpaValidator {
v => v.variable.$refText === context2.assignedValues[i].variable.$refText
);
if (
varIndex === -1 ||
varIndex !== -1 &&
context1.assignedValues[varIndex].value.$refText !== context2.assignedValues[i].value.$refText
) {
isSame = false;
Expand Down
Loading