Skip to content

Commit 6a73bbc

Browse files
adaussyAxelRICHARD
authored andcommitted
[1617] Incorrect resolution of redefined attribute during import
Bug: #1617 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
1 parent 208e5d4 commit 6a73bbc

4 files changed

Lines changed: 194 additions & 4 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Also introduces new `ServiceMethod` helper class to build AQL service call expre
5656
- https://github.com/eclipse-syson/syson/issues/1611[#1611] [services] Fix an issue where the undo of the deletion of a graphical node in a diagram was not restoring the graphical node correctly.
5757
- https://github.com/eclipse-syson/syson/issues/1618[#1618] [rest-apis] Fix an issue where the _eAnnotations_ reference coming from the Ecore metamodel was taking into account by REST APIs serialization.
5858
- https://github.com/eclipse-syson/syson/issues/1621[#1621] [diagrams] Fix an issue where the creation of a _Package_ inside another _Package_ created the _Package_ at the root of the diagram in addition to the inside of the target _Package_.
59+
- https://github.com/eclipse-syson/syson/issues/1617[#1617] [import] Fix incorrect resolution of _redefined feature_ on `Redefinitions` during textual import.
5960

6061
=== Improvements
6162

backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,64 @@ public void tearDown() {
149149
TestTransaction.start();
150150
}
151151

152+
@Test
153+
@DisplayName("Given of model with redefinition depending on inherited memberships computation, WHEN importing the model, THEN redefined feature should resolve properly using inherited memberships")
154+
public void checkRedefinedFeatureToInheritedFields() throws IOException {
155+
var input = """
156+
private import ISQBase::mass;
157+
#MP part p2 {
158+
attribute :>> mass = 2.0;
159+
}
160+
private import Metaobjects::SemanticMetadata;
161+
part p {
162+
attribute mass;
163+
}
164+
metadata def MP :> SemanticMetadata {
165+
:>> baseType = p meta SysML::Systems::PartUsage;
166+
:> annotatedElement : SysML::Systems::PartUsage;
167+
}
168+
part p3 :> p1::p2::p0 {
169+
attribute :>> mass = 2.0;
170+
}
171+
package p1 {
172+
package p2 {
173+
part p0 :> p;
174+
}
175+
}
176+
part p {
177+
attribute mass;
178+
}
179+
""";
180+
181+
this.checker.checkImportedModel(resource -> {
182+
Optional<AttributeUsage> pMass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
183+
.filter(partUsage -> "p::mass".equals(partUsage.getQualifiedName()))
184+
.findFirst();
185+
186+
assertThat(pMass)
187+
.isPresent();
188+
189+
// Checks that p2::mass redefined p::mass and not ISQBase::mass because of the use of the semantic MetadataDefinition that implicitly makes p2 subset p
190+
Optional<AttributeUsage> p2Mass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
191+
.filter(partUsage -> "p2::mass".equals(partUsage.getQualifiedName()))
192+
.findFirst();
193+
assertThat(p2Mass)
194+
.isPresent();
195+
196+
assertThat(p2Mass.get()).matches(attributeUsage -> attributeUsage.redefines(pMass.get()));
197+
198+
// Checks that p2::mass redefined p::mass and not ISQBase::mass because p3 subsets p1::p2::p0 and p0 subsets p
199+
Optional<AttributeUsage> p3Mass = EMFUtils.allContainedObjectOfType(resource, AttributeUsage.class)
200+
.filter(partUsage -> "p3::mass".equals(partUsage.getQualifiedName()))
201+
.findFirst();
202+
assertThat(p2Mass)
203+
.isPresent();
204+
205+
assertThat(p2Mass.get()).matches(attributeUsage -> attributeUsage.redefines(pMass.get()));
206+
207+
}).check(input);
208+
}
209+
152210
@Test
153211
@DisplayName("GIVEN a model with a FlowUsage using a FeatureChain, WHEN importing the model, THEN the source and the target of the FlowUsage should be properly resolved")
154212
public void checkFlowUsageWithFeatureChain() throws IOException {

backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/parser/AstTreeParser.java

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@
1919
import java.util.Comparator;
2020
import java.util.Iterator;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.Map.Entry;
24+
import java.util.stream.Collectors;
2325
import java.util.stream.StreamSupport;
2426

2527
import org.eclipse.emf.ecore.EObject;
2628
import org.eclipse.emf.ecore.EReference;
29+
import org.eclipse.emf.ecore.InternalEObject;
2730
import org.eclipse.syson.sysml.Element;
31+
import org.eclipse.syson.sysml.Import;
32+
import org.eclipse.syson.sysml.Redefinition;
33+
import org.eclipse.syson.sysml.Specialization;
2834
import org.eclipse.syson.sysml.helper.EMFUtils;
2935
import org.eclipse.syson.sysml.parser.translation.EClassifierTranslator;
3036
import org.eclipse.syson.sysml.utils.LogNameProvider;
@@ -157,6 +163,83 @@ private void handleNodeValue(final EObject eObject, String key, JsonNode value)
157163
}
158164

159165
public void resolveAllReference(List<ProxiedReference> unresolvedReferences) {
166+
/*
167+
* We need to order some of the proxies to resolve. For example, redefinition resolution should be handled once
168+
* all specializations have been handled. Indeed, the name resolution of a redefined feature on Redefinition can
169+
* depend on either the Subclassification or Subsetting of its owning Namespace (that might be implied through
170+
* the use of semantic MetadataUsage) or the type of the owning namespace</li> </ul></p>
171+
*/
172+
Map<ReferenceType, List<ProxiedReference>> partitionedUnresolvedReferences = unresolvedReferences.stream().collect(Collectors.groupingBy(this::getProxyClassification));
173+
174+
// First resolve imports
175+
List<ProxiedReference> unresolvedProxiesAfterImport = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.IMPORT,
176+
"Import resolution phase", List.of());
177+
178+
// Then resolve unclassified proxies and the previous unresolved proxies
179+
List<ProxiedReference> unresolvedProxiesAfterUnqualified = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.UNCLASSIFIED,
180+
"Non qualified proxy resolution phase", unresolvedProxiesAfterImport);
181+
182+
// Then resolve specializations that are not redefinitions since redefinition needs to access inherited memberships
183+
List<ProxiedReference> unresolvedAfterSpecialization = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.OTHER_SPECIALIZATION,
184+
"Non redefinition specialization proxy resolution phase",
185+
unresolvedProxiesAfterUnqualified);
186+
187+
// Then resolve base_type redefinition proxies stored MetadataDefinition since they may impact implicit inherited members
188+
List<ProxiedReference> unresolvedAfterBaseTypeRedefinition = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.BASE_TYPE_METADATA_DEFINITION_REDEFINITION,
189+
"Semantic MetadataData base_type redefinition proxy resolution phase", unresolvedAfterSpecialization);
190+
191+
// At the end resolve redefinition
192+
List<ProxiedReference> unresolvedAfterRedefinition = this.doResolveAllReference(partitionedUnresolvedReferences, ReferenceType.OTHER_REDEFINITION, "Other redefinition proxy resolution phase",
193+
unresolvedAfterBaseTypeRedefinition);
194+
195+
if (!unresolvedAfterRedefinition.isEmpty()) {
196+
this.handleUnresolvableProxies(unresolvedAfterRedefinition);
197+
}
198+
}
199+
200+
private ReferenceType getProxyClassification(ProxiedReference proxyReference) {
201+
EObject owner = proxyReference.owner();
202+
InternalEObject targetProxy = proxyReference.targetProxy();
203+
final ReferenceType result;
204+
if (owner instanceof Redefinition && targetProxy.toString().contains("baseType")) {
205+
result = ReferenceType.BASE_TYPE_METADATA_DEFINITION_REDEFINITION;
206+
} else if (owner instanceof Import) {
207+
result = ReferenceType.IMPORT;
208+
} else if (owner instanceof Redefinition) {
209+
result = ReferenceType.OTHER_REDEFINITION;
210+
} else if (owner instanceof Specialization) {
211+
result = ReferenceType.OTHER_SPECIALIZATION;
212+
} else {
213+
result = ReferenceType.UNCLASSIFIED;
214+
}
215+
return result;
216+
}
217+
218+
/**
219+
* Runs a resolution phase of a given class of proxy
220+
*
221+
* @param classifiedProxies
222+
* all the proxy classified
223+
* @param proxyClass
224+
* the type of proxy to resolve
225+
* @param resolutionPhaseName
226+
* a name of resolution phase
227+
* @param yetNotResolved
228+
* A list of unresolved proxy from the previous phases
229+
* @return all proxies that have not been resolved
230+
*/
231+
private List<ProxiedReference> doResolveAllReference(Map<ReferenceType, List<ProxiedReference>> classifiedProxies, ReferenceType proxyClass, String resolutionPhaseName,
232+
List<ProxiedReference> yetNotResolved) {
233+
List<ProxiedReference> toResolve = classifiedProxies.get(proxyClass);
234+
if (toResolve != null && !toResolve.isEmpty()) {
235+
List<ProxiedReference> tmpToResolve = new ArrayList<>(toResolve);
236+
tmpToResolve.addAll(yetNotResolved);
237+
return this.doResolveAllReference(tmpToResolve, resolutionPhaseName);
238+
}
239+
return yetNotResolved;
240+
}
241+
242+
private List<ProxiedReference> doResolveAllReference(List<ProxiedReference> unresolvedReferences, String resolutionPhase) {
160243
// Sorts proxies to resolve by breadth-first search strategy. This way we resolve first the import
161244
// generally located at top level part of the model. Those proxies are often used by lowest part of the
162245
// model to resolve their link
@@ -172,18 +255,19 @@ public void resolveAllReference(List<ProxiedReference> unresolvedReferences) {
172255
List<ProxiedReference> proxiedReferences = unresolvedReferencesTmp.stream()
173256
.filter(proxiedReference -> !this.proxyResolver.resolveProxy(proxiedReference))
174257
.toList();
175-
LOGGER.info(MessageFormat.format("{0} remaining proxies to resolve after {1} try", Integer.toString(proxiedReferences.size()), Integer.toString(tryNb)));
258+
LOGGER.info(MessageFormat.format("{2} : {0} remaining proxies to resolve after {1} try", Integer.toString(proxiedReferences.size()), Integer.toString(tryNb), resolutionPhase));
176259
if (proxiedReferences.equals(unresolvedReferencesTmp)) {
177-
this.printResolutionError(proxiedReferences);
178260
break;
179261
} else {
180262
unresolvedReferencesTmp = proxiedReferences;
181263
}
182264
}
265+
266+
return unresolvedReferencesTmp;
183267
}
184268

185-
private void printResolutionError(List<ProxiedReference> proxiedReferences) {
186-
for (ProxiedReference pr : proxiedReferences) {
269+
private void handleUnresolvableProxies(List<ProxiedReference> unresolvableProxies) {
270+
for (ProxiedReference pr : unresolvableProxies) {
187271
String msg = MessageFormat.format("Unable to resolve name ''{1}'' for reference ''{2}'' on element ''{0}''", this.logNameProvider.getName(pr.owner()),
188272
pr.targetProxy().eProxyURI().fragment(),
189273
pr.reference().getName());
@@ -233,4 +317,30 @@ public int compare(EObject owner1, EObject owner2) {
233317
return signum;
234318
}
235319
}
320+
321+
/**
322+
* Classification of proxy to resolve.
323+
*/
324+
private enum ReferenceType {
325+
/**
326+
* A proxy related to imports.
327+
*/
328+
IMPORT,
329+
/**
330+
* All proxies that do not belong to any other categories.
331+
*/
332+
UNCLASSIFIED,
333+
/**
334+
* Proxy of the "base_type" redefinition feature of the "SemanticMetadata" used in MetadataDefinition.
335+
*/
336+
BASE_TYPE_METADATA_DEFINITION_REDEFINITION,
337+
/**
338+
* All other redefinitions except the one stored in BASE_TYPE_METADATA_DEFINITION_REDEFINITION.
339+
*/
340+
OTHER_REDEFINITION,
341+
/**
342+
* All specializations except the Redefinition.
343+
*/
344+
OTHER_SPECIALIZATION
345+
}
236346
}

doc/content/modules/user-manual/pages/release-notes/2025.12.0.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ requirement <'SN'> {
4040
- Fix an issue where the undo of the deletion of a graphical node in a diagram was not restoring the graphical node correctly.
4141
- Fix an issue where the _eAnnotations_ reference coming from the Ecore metamodel was taking into account by REST APIs serialization.
4242
- In diagrams, fix an issue where the creation of a _Package_ inside another _Package_ created the _Package_ at the root of the diagram in addition to the inside of the target _Package_.
43+
- Fix invalid resolution during textual import of _redefined Feature_ on `Redefinition`.
44+
This problem appears in cases of a name conflict between a global available item and an item accessible via _inherited membership_ such as in the following model:
45+
46+
```
47+
private import ISQBase::mass; // Gives global access to "mass"
48+
49+
#MP part p2 { // Implicit subsetting "p" using a semantic MetadataDefinition
50+
attribute :>> mass = 2.0; // Should redefine "p::mass" and not "ISQBase::mass"
51+
}
52+
53+
private import Metaobjects::SemanticMetadata;
54+
55+
part p {
56+
attribute mass;
57+
}
58+
59+
metadata def MP :> SemanticMetadata {
60+
:>> baseType = p meta SysML::PartUsage;
61+
:> annotatedElement : SysML::PartUsage;
62+
}
63+
```
4364

4465
== Improvements
4566

0 commit comments

Comments
 (0)