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
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Prevent the feature chain computer from traversing the whole standard library to
- https://github.com/eclipse-syson/syson/issues/2486[#2486] [import/export] Resolve cross-document references for nested SysML files.
- https://github.com/eclipse-syson/syson/issues/2466[#2466] [diagrams] Fix subclassification edge tool in _General View_ diagrams.
- https://github.com/eclipse-syson/syson/issues/2485[#2485] [diagrams] Fix the _New Derived Requirement_ edge tool creating a derivation between two `RequirementUsage`.
- https://github.com/eclipse-syson/syson/issues/2474[#2474] [diagrams] Do not offer expression tools on inherited border nodes or inherited compartment items.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
import org.eclipse.sirius.components.diagrams.Edge;
import org.eclipse.sirius.components.diagrams.Node;
import org.eclipse.sirius.components.diagrams.description.IDiagramElementDescription;
import org.eclipse.sirius.components.diagrams.description.NodeDescription;
import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext;
import org.eclipse.sirius.components.view.emf.diagram.api.IViewDiagramDescriptionSearchService;
import org.eclipse.sirius.components.palette.dto.ITool;
import org.eclipse.sirius.components.palette.dto.ToolSection;
import org.eclipse.sirius.components.view.emf.diagram.api.IPaletteToolsProvider;
Expand All @@ -50,6 +52,10 @@
@Service
public class ExpressionsPaletteToolsProvider implements IPaletteToolsProvider {

private static final String INHERITED_BORDER_NODE_DESCRIPTION_NAME_FRAGMENT = "InheritedBorderNode";

private static final String INHERITED_COMPARTMENT_ITEM_DESCRIPTION_NAME_FRAGMENT = "InheritedCompartmentItem";

private static final String NEW_EXPRESSION_TOOL_ID = "tool_new_expression";

private static final String NEW_EXPRESSION_TOOL_LABEL = "New expression";
Expand All @@ -70,21 +76,49 @@ public class ExpressionsPaletteToolsProvider implements IPaletteToolsProvider {

private final IReadOnlyObjectPredicate readOnlyObjectPredicate;

private final IViewDiagramDescriptionSearchService viewDiagramDescriptionSearchService;

private final MetamodelQueryElementService metamodelQueryElementService;

public ExpressionsPaletteToolsProvider(IObjectSearchService objectSearchService, IReadOnlyObjectPredicate readOnlyObjectPredicate) {
/**
* Creates the provider.
*
* @param objectSearchService
* the service used to retrieve diagram semantic targets
* @param readOnlyObjectPredicate
* the predicate used to identify read-only semantic targets
* @param viewDiagramDescriptionSearchService
* the service used to resolve runtime descriptions to their View descriptions
*/
public ExpressionsPaletteToolsProvider(IObjectSearchService objectSearchService, IReadOnlyObjectPredicate readOnlyObjectPredicate,
IViewDiagramDescriptionSearchService viewDiagramDescriptionSearchService) {
this.objectSearchService = Objects.requireNonNull(objectSearchService);
this.readOnlyObjectPredicate = Objects.requireNonNull(readOnlyObjectPredicate);
this.viewDiagramDescriptionSearchService = Objects.requireNonNull(viewDiagramDescriptionSearchService);
this.metamodelQueryElementService = new MetamodelQueryElementService();
}

/**
* Contributes expression tools only when the selected diagram element is not inherited content.
*
* @param editingContext
* the current editing context
* @param diagramContext
* the current diagram context
* @param diagramElementDescription
* the selected diagram element description
* @param diagramElement
* the selected diagram element
* @return the expression tool section, or no section when no expression action applies
*/
@Override
public List<ToolSection> createExtraToolSections(IEditingContext editingContext, DiagramContext diagramContext, Object diagramElementDescription, Object diagramElement) {
var tools = new ArrayList<ITool>();

var optionalTargetObjectId = this.getTargetObjectId(diagramElement);

if (optionalTargetObjectId.isPresent() && diagramElementDescription instanceof IDiagramElementDescription nodeDescription) {
if (optionalTargetObjectId.isPresent() && diagramElementDescription instanceof IDiagramElementDescription nodeDescription
&& !this.isInheritedNodeDescription(editingContext, nodeDescription)) {
var semanticObject = this.objectSearchService.getObject(editingContext, optionalTargetObjectId.get());
if (semanticObject.isPresent() && semanticObject.get() instanceof Element element) {
if (editingContext instanceof IEMFEditingContext emfEditingContext && this.canHaveNewExpression(emfEditingContext, element)) {
Expand Down Expand Up @@ -116,6 +150,27 @@ private Optional<String> getTargetObjectId(Object diagramElement) {
return result;
}

/**
* Indicates whether the description represents inherited content, which must not expose expression mutation
* tools.
*
* @param editingContext
* the current editing context
* @param diagramElementDescription
* the description of the selected diagram element
* @return {@code true} when the description represents an inherited border node or compartment item
*/
private boolean isInheritedNodeDescription(IEditingContext editingContext, IDiagramElementDescription diagramElementDescription) {
if (diagramElementDescription instanceof NodeDescription nodeDescription) {
return this.viewDiagramDescriptionSearchService.findViewNodeDescriptionById(editingContext, nodeDescription.getId())
.map(org.eclipse.sirius.components.view.diagram.NodeDescription::getName)
.map(descriptionName -> descriptionName.contains(INHERITED_BORDER_NODE_DESCRIPTION_NAME_FRAGMENT)
|| descriptionName.contains(INHERITED_COMPARTMENT_ITEM_DESCRIPTION_NAME_FRAGMENT))
.orElse(false);
}
return false;
}

private boolean canHaveNewExpression(IEMFEditingContext editingContext, Element element) {
return !this.readOnlyObjectPredicate.test(element) && this.metamodelQueryElementService.canContainExpressionDefinition(element)
&& !this.metamodelQueryElementService.hasSingleExpressionDefinition(element);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*******************************************************************************
* Copyright (c) 2026 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.syson.application.expressions.services;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.List;
import java.util.Optional;

import org.assertj.core.api.InstanceOfAssertFactories;
import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext;
import org.eclipse.sirius.components.core.api.IObjectSearchService;
import org.eclipse.sirius.components.core.api.IReadOnlyObjectPredicate;
import org.eclipse.sirius.components.diagrams.Node;
import org.eclipse.sirius.components.diagrams.description.NodeDescription;
import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext;
import org.eclipse.sirius.components.palette.dto.ToolSection;
import org.eclipse.sirius.components.view.diagram.DiagramFactory;
import org.eclipse.sirius.components.view.emf.diagram.api.IViewDiagramDescriptionSearchService;
import org.eclipse.syson.sysml.AttributeUsage;
import org.eclipse.syson.sysml.SysmlFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Tests the expression tools contributed to diagram palettes.
*
* @author arichard
*/
class ExpressionsPaletteToolsProviderTest {

private static final String ELEMENT_ID = "attribute-id";

private IEMFEditingContext editingContext;

private IObjectSearchService objectSearchService;

private Node node;

private IViewDiagramDescriptionSearchService viewDiagramDescriptionSearchService;

private ExpressionsPaletteToolsProvider provider;

/**
* Initializes the provider with an expression-capable attribute target.
*/
@BeforeEach
void setUp() {
this.editingContext = mock(IEMFEditingContext.class);
this.objectSearchService = mock(IObjectSearchService.class);
IReadOnlyObjectPredicate readOnlyObjectPredicate = mock(IReadOnlyObjectPredicate.class);
this.viewDiagramDescriptionSearchService = mock(IViewDiagramDescriptionSearchService.class);
this.node = mock(Node.class);

AttributeUsage attributeUsage = SysmlFactory.eINSTANCE.createAttributeUsage();
when(this.node.getTargetObjectId()).thenReturn(ELEMENT_ID);
when(this.objectSearchService.getObject(this.editingContext, ELEMENT_ID)).thenReturn(Optional.of(attributeUsage));
when(readOnlyObjectPredicate.test(attributeUsage)).thenReturn(false);

this.provider = new ExpressionsPaletteToolsProvider(this.objectSearchService, readOnlyObjectPredicate, this.viewDiagramDescriptionSearchService);
}

/**
* Verifies that expression tools remain available on an owned diagram node.
*/
@Test
void expressionToolsAreAvailableOnOwnedNode() {
NodeDescription nodeDescription = this.nodeDescription("description-id");

List<ToolSection> toolSections = this.provider.createExtraToolSections(this.editingContext, mock(DiagramContext.class), nodeDescription, this.node);

assertThat(toolSections).singleElement().extracting(ToolSection::tools).asInstanceOf(InstanceOfAssertFactories.LIST)
.extracting("id")
.containsExactly("tool_new_expression");
}

/**
* Verifies that inherited border nodes do not expose expression mutation tools.
*/
@Test
void expressionToolsAreUnavailableOnInheritedBorderNode() {
NodeDescription nodeDescription = this.nodeDescription("inherited-border-node-description-id");
this.mockViewNodeDescription(nodeDescription, "GV InheritedBorderNode AttributeUsage");

List<ToolSection> toolSections = this.provider.createExtraToolSections(this.editingContext, mock(DiagramContext.class), nodeDescription, this.node);

assertThat(toolSections).isEmpty();
}

/**
* Verifies that inherited compartment items do not expose expression mutation tools.
*/
@Test
void expressionToolsAreUnavailableOnInheritedCompartmentItem() {
NodeDescription nodeDescription = this.nodeDescription("inherited-compartment-item-description-id");
this.mockViewNodeDescription(nodeDescription, "GV InheritedCompartmentItem AttributeUsage");

List<ToolSection> toolSections = this.provider.createExtraToolSections(this.editingContext, mock(DiagramContext.class), nodeDescription, this.node);

assertThat(toolSections).isEmpty();
}

/**
* Creates a diagram node description with the given name.
*
* @param name
* the description name
* @return the mocked node description
*/
private NodeDescription nodeDescription(String id) {
NodeDescription nodeDescription = mock(NodeDescription.class);
when(nodeDescription.getId()).thenReturn(id);
return nodeDescription;
}

/**
* Associates a runtime node description with its View description.
*
* @param nodeDescription
* the runtime node description
* @param name
* the View node description name
*/
private void mockViewNodeDescription(NodeDescription nodeDescription, String name) {
org.eclipse.sirius.components.view.diagram.NodeDescription viewNodeDescription = DiagramFactory.eINSTANCE.createNodeDescription();
viewNodeDescription.setName(name);
when(this.viewDiagramDescriptionSearchService.findViewNodeDescriptionById(this.editingContext, nodeDescription.getId()))
.thenReturn(Optional.of(viewNodeDescription));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ image::release-notes-port-inherited-item-border-nodes.png[ItemUsage border node
The creation of a new `Element` remains available.
** Add the capability of selecting an existing `PortUsage` in _New Port as Receiver_ tool on `AcceptAction` graphical nodes.
The creation of a new `PortUsage` remains available.

== Bug fixes

* In diagrams:
Expand All @@ -118,6 +119,7 @@ The affected connections were missing their source and target and were therefore
** Do not propose the _New Subclassification_ edge tool from `EnumerationDefinition` graphical nodes' palette, which cannot subclassify another `EnumerationDefinition`.
** Do not propose the _New Subclassification_ edge tool in the palette when targeting a `Usage` graphical node from a `Definition` graphical node.
** Fix the edge tool creating a `ConnectionUsage` between an `ActionUsage` graphical node and the _done_ `ActionUsage` graphical node.
** Inherited border nodes and inherited compartment items no longer offer the _New Expression_, _Edit Expression_, or _Delete Expression_ actions.

* In all views:

Expand Down
Loading