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 CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ This fix ensure that imported models containing, for example, a top-level `Libra
- https://github.com/eclipse-syson/syson/issues/1799[#1799] During export, `Element` with a name that conflicts with SysML Keyword should be escaped.
- https://github.com/eclipse-syson/syson/issues/1825[#1825] `LiteralRational` value wrongly rounded during textual export.
- https://github.com/eclipse-syson/syson/issues/1784[#1784] Error while exporting `FeatureValue` using enumeration literals.

- https://github.com/eclipse-syson/syson/issues/1838[#1838] [metamodel] Invalid computation of `Usage.getType`: The subsetted features should be taken into account.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ public void directEditFeatureTyping() {
this.directEditTester.checkDirectEditCenteredEdgeLabel(this.verifier, this.diagram, EdgeConnectionUsageTestProjectData.GraphicalIds.CONNECTION_0_ID, "connection0 : ConnectionDefinition1", "connection0 : ConnectionDefinition1");

this.semanticCheckerService.checkElement(this.verifier, ConnectionUsage.class, () -> EdgeConnectionUsageTestProjectData.SemanticIds.CONNECTION_0_ID, connectionUsage -> {
assertThat(connectionUsage.getType()).hasSize(1).allMatch(type -> "ConnectionDefinition1".equals(type.getName()));
assertThat(connectionUsage.getType()).hasSize(2)
.allMatch(type -> "ConnectionDefinition1".equals(type.getName()) || "BinaryLinkObject".equals(type.getName()));
});

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.eclipse.syson.sysml.AttributeUsage;
import org.eclipse.syson.sysml.Element;
import org.eclipse.syson.sysml.Feature;
import org.eclipse.syson.sysml.FeatureTyping;
import org.eclipse.syson.sysml.FlowUsage;
import org.eclipse.syson.sysml.Import;
import org.eclipse.syson.sysml.NamespaceImport;
Expand Down Expand Up @@ -207,9 +208,9 @@ public void testCreateRealAttributeWithResolutionProblem() {
if (p.getOwnedFeature().size() == 1) {
Feature f1 = p.getOwnedFeature().get(0);
if (f1 instanceof AttributeUsage xAttr) {
EList<Type> types = xAttr.getType();
EList<FeatureTyping> typing = xAttr.getOwnedTyping();
// The FeatureTyping is created but the type is unresolved
return types.size() == 1 && types.get(0) == null && "x".equals(f1.getDeclaredName());
return typing.size() == 1 && typing.get(0).getType() == null && "x".equals(f1.getDeclaredName());
}
}
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2025 Obeo.
* Copyright (c) 2025, 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
Expand Down Expand Up @@ -216,9 +216,7 @@ public void testProjectContentsHaveBeenUpdated() {
// So the type should be null.
final AttributeUsage attribute1 = Streams.of(mainResource.getAllContents()).filter(AttributeUsage.class::isInstance).map(AttributeUsage.class::cast)
.filter(attribute -> attribute.getDeclaredName().equals("attribute1")).findFirst().get();
assertThat(attribute1.getType()).hasSize(1);
final Type attribute1Type = attribute1.getType().get(0);
assertThat(attribute1Type).isNull();
assertThat(attribute1.getType()).isEmpty();

// LibraryResource2 is unchanged, so our assumptions should still hold.
final AttributeUsage attribute2 = Streams.of(mainResource.getAllContents()).filter(AttributeUsage.class::isInstance).map(AttributeUsage.class::cast)
Expand All @@ -232,9 +230,7 @@ public void testProjectContentsHaveBeenUpdated() {
// AttributeDefinition3 has been removed, so the type should be null now.
final AttributeUsage attribute3 = Streams.of(mainResource.getAllContents()).filter(AttributeUsage.class::isInstance).map(AttributeUsage.class::cast)
.filter(attribute -> attribute.getDeclaredName().equals("attribute3")).findFirst().get();
assertThat(attribute3.getType()).hasSize(1);
final Type attribute3Type = attribute3.getType().get(0);
assertThat(attribute3Type).isNull();
assertThat(attribute3.getType()).isEmpty();
}

protected Library loadMyLibraryV1() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*******************************************************************************
* 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.metamodel.helper;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Duration;
import java.util.UUID;

import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
import org.eclipse.sirius.components.core.api.IObjectSearchService;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.eclipse.syson.AbstractIntegrationTests;
import org.eclipse.syson.application.controller.editingContext.checkers.ISemanticChecker;
import org.eclipse.syson.application.controller.editingContext.checkers.SemanticCheckerService;
import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData;
import org.eclipse.syson.services.SemanticRunnableFactory;
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
import org.eclipse.syson.sysml.Package;
import org.eclipse.syson.sysml.SysmlFactory;
import org.eclipse.syson.sysml.ViewDefinition;
import org.eclipse.syson.sysml.ViewUsage;
import org.eclipse.syson.sysml.metamodel.services.ElementInitializerSwitch;
import org.eclipse.syson.sysml.metamodel.services.MetamodelMutationElementService;
import org.eclipse.syson.sysml.util.ElementUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.transaction.annotation.Transactional;

import reactor.test.StepVerifier;

/**
* Integration test for {@link org.eclipse.syson.sysml.metamodel.services.ElementInitializerSwitch}. Mostly used to test element initialization in a context where the standard libraries are required.
*
* @author Arthur Daussy
*/
@Transactional
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ElementInitializerSwitchIntegrationTest extends AbstractIntegrationTests {

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private IGivenDiagramSubscription givenDiagramSubscription;

@Autowired
private IObjectSearchService objectSearchService;

@Autowired
private SemanticRunnableFactory semanticRunnableFactory;

private StepVerifier.Step<DiagramRefreshedEventPayload> verifier;

private SemanticCheckerService semanticCheckerService;

private ElementInitializerSwitch elementInitializerSwitch;

private ElementUtil elementUtil;

@BeforeEach
public void setUp() {
this.elementInitializerSwitch = new ElementInitializerSwitch();
this.elementUtil = new ElementUtil();
this.givenInitialServerState.initialize();
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID);
var flux = this.givenDiagramSubscription.subscribe(diagramEventInput);
this.verifier = StepVerifier.create(flux);
this.semanticCheckerService = new SemanticCheckerService(this.semanticRunnableFactory, this.objectSearchService, GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID,
GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID);
}

@AfterEach
public void tearDown() {
if (this.verifier != null) {
this.verifier.thenCancel()
.verify(Duration.ofSeconds(10));
}
}

@DisplayName("GIVEN a ViewUsage, WHEN it is initialized, THEN it's typed by default with the GeneralView ViewDefinition")
@Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
@Test
public void testViewUsageDefaultType() {
ISemanticChecker semanticChecker = (editingContext) -> {
Package semanticRootObject = (Package) this.objectSearchService.getObject(editingContext, GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID).orElseThrow();
var view1 = SysmlFactory.eINSTANCE.createViewUsage();
new MetamodelMutationElementService().addChildInParent(semanticRootObject, view1);
var initializedView1 = this.elementInitializerSwitch.doSwitch(view1);
var generalViewViewDef = this.elementUtil.findByNameAndType(view1, "StandardViewDefinitions::GeneralView", ViewDefinition.class);
assertThat(((ViewUsage) initializedView1).getType()).contains(generalViewViewDef);
};
this.semanticCheckerService.checkEditingContext(semanticChecker, this.verifier);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
package org.eclipse.syson.sysml.impl;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.BasicEList;
Expand Down Expand Up @@ -868,14 +874,10 @@ public Type basicGetOwningType() {
*/
@Override
public EList<Type> getType() {
List<Type> types = new ArrayList<>();
this.getOwnedSpecialization().stream()
.filter(FeatureTyping.class::isInstance)
.map(FeatureTyping.class::cast)
.map(typing -> typing.getType())
.forEach(types::add);

return new EcoreEList.UnmodifiableEList<>(this, SysmlPackage.eINSTANCE.getFeature_Type(), types.size(), types.toArray());
Collection<Feature> allTypingFeatures = this.collectTypingFeatures();
List<Type> types = this.collectTypes(allTypingFeatures);
List<Type> filtered = this.reduceToMostSpecializeTypes(types);
return new EcoreEList.UnmodifiableEList<>(this, SysmlPackage.eINSTANCE.getFeature_Type(), filtered.size(), filtered.toArray());
}

/**
Expand Down Expand Up @@ -1130,8 +1132,7 @@ public EList<Feature> typingFeatures() {
if (!this.isIsConjugated()) {
var subsettedFeatures = this.getOwnedSubsetting().stream()
.filter(s -> !(s instanceof CrossSubsetting))
.map(Subsetting::getSubsettedFeature)
.toList();
.map(Subsetting::getSubsettedFeature).collect(Collectors.toList());
var chainingFeature = this.getChainingFeature();
if (chainingFeature.isEmpty() || subsettedFeatures.contains(chainingFeature.get(chainingFeature.size() - 1))) {
typingFeatures.addAll(subsettedFeatures);
Expand Down Expand Up @@ -1496,4 +1497,46 @@ public String toString() {
return result.toString();
}

/**
* @generated NOT
*/
private List<Type> reduceToMostSpecializeTypes(List<Type> types) {
return types.stream()
.filter(t1 -> types.stream().noneMatch(t2 -> t2 != t1 && t2.specializes(t1)))
.toList();
}

/**
* @generated NOT
*/
private List<Type> collectTypes(Collection<Feature> features) {
return features.stream()
.flatMap(feature -> feature.getOwnedTyping().stream()
.map(FeatureTyping::getType)
.filter(Objects::nonNull))
.distinct()
.toList();
}

/**
* @generated NOT
*/
private Collection<Feature> collectTypingFeatures() {
Deque<Feature> typingFeatureQueue = new ArrayDeque<>();
typingFeatureQueue.push(this);
Set<Feature> featureCollector = new LinkedHashSet<>();
featureCollector.add(this);

// Collect all features providing a type for this feature
while (!typingFeatureQueue.isEmpty()) {
Feature current = typingFeatureQueue.pop();
// Collect all features providing typing
for (Feature next : current.typingFeatures()) {
if (next != null && featureCollector.add(next)) {
typingFeatureQueue.addLast(next);
}
}
}
return featureCollector;
}
} // FeatureImpl
Loading
Loading