Skip to content

Commit 5a58f52

Browse files
committed
[1627] Invalid textual import/export of LiteralRational
Bug: #1627 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
1 parent 6191bc1 commit 5a58f52

6 files changed

Lines changed: 236 additions & 13 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Also introduces new `ServiceMethod` helper class to build AQL service call expre
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_.
5959
- https://github.com/eclipse-syson/syson/issues/1617[#1617] [import] Fix incorrect resolution of _redefined feature_ on `Redefinitions` during textual import.
60+
- https://github.com/eclipse-syson/syson/issues/1627[#1627] [import] [export] Fix invalid textual import/export of `LiteralRational`.
6061

6162
=== Improvements
6263

backend/application/syson-application/src/test/java/org/eclipse/syson/application/export/ImportExportTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,12 +678,37 @@ public void checkScalarValueAttribute() throws IOException {
678678
attribute c : Integer = 1;
679679
attribute d : Integer := 3;
680680
attribute e : MyEnum default = enum1;
681+
attribute f : Real = 1.0;
682+
attribute g = 3.14;
683+
attribute h = 2.5E-10;
684+
attribute i = .5;
685+
attribute j = 1E+3;
681686
}
682687
enum def MyEnum {
683688
enum1;
684689
}
685690
}""";
686-
this.checker.check(input, input);
691+
// We use scientific notation for real outside of range [1E-3,1E6[
692+
var expected = """
693+
package Occurrences {
694+
private import ScalarValues::*;
695+
occurrence def Occurrence1 {
696+
attribute a : Integer;
697+
attribute b : Integer default = 1;
698+
attribute c : Integer = 1;
699+
attribute d : Integer := 3;
700+
attribute e : MyEnum default = enum1;
701+
attribute f : Real = 1.0;
702+
attribute g = 3.14;
703+
attribute h = 2.5E-10;
704+
attribute i = 0.5;
705+
attribute j = 1000.0;
706+
}
707+
enum def MyEnum {
708+
enum1;
709+
}
710+
}""";
711+
this.checker.check(input, expected);
687712
}
688713

689714
/**

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.eclipse.syson.sysml.FeatureValue;
5555
import org.eclipse.syson.sysml.FlowUsage;
5656
import org.eclipse.syson.sysml.LiteralInteger;
57+
import org.eclipse.syson.sysml.LiteralRational;
5758
import org.eclipse.syson.sysml.LiteralString;
5859
import org.eclipse.syson.sysml.Membership;
5960
import org.eclipse.syson.sysml.MetadataAccessExpression;
@@ -203,7 +204,41 @@ public void checkRedefinedFeatureToInheritedFields() throws IOException {
203204
.isPresent();
204205

205206
assertThat(p2Mass.get()).matches(attributeUsage -> attributeUsage.redefines(pMass.get()));
207+
});
208+
}
209+
210+
@Test
211+
@DisplayName("GIVEN a model with rational numbers, WHEN importing the model, THEN the value should use LiteralRational")
212+
public void checkLiteralRationalImport() throws IOException {
213+
var input = """
214+
private import ScalarValues::*;
215+
part p1 {
216+
attribute a : Real = 1.0;
217+
attribute b = 3.14;
218+
attribute c = 2.5E-10;
219+
attribute d = .5;
220+
attribute e = 1E+3;
221+
}""";
206222

223+
this.checker.checkImportedModel(resource -> {
224+
List<FeatureValue> values = EMFUtils.allContainedObjectOfType(resource, FeatureValue.class)
225+
.toList();
226+
assertThat(values).hasSize(5);
227+
assertThat(values.get(0).getValue())
228+
.isInstanceOf(LiteralRational.class)
229+
.matches(v -> ((LiteralRational) v).getValue() == 1.0);
230+
assertThat(values.get(1).getValue())
231+
.isInstanceOf(LiteralRational.class)
232+
.matches(v -> ((LiteralRational) v).getValue() == 3.14);
233+
assertThat(values.get(2).getValue())
234+
.isInstanceOf(LiteralRational.class)
235+
.matches(v -> ((LiteralRational) v).getValue() == 2.5E-10);
236+
assertThat(values.get(3).getValue())
237+
.isInstanceOf(LiteralRational.class)
238+
.matches(v -> ((LiteralRational) v).getValue() == 0.5);
239+
assertThat(values.get(4).getValue())
240+
.isInstanceOf(LiteralRational.class)
241+
.matches(v -> ((LiteralRational) v).getValue() == 1E+3);
207242
}).check(input);
208243
}
209244

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
import com.fasterxml.jackson.databind.JsonNode;
1616

17-
import java.math.BigDecimal;
18-
1917
import org.eclipse.emf.ecore.EClass;
2018
import org.eclipse.emf.ecore.EClassifier;
2119
import org.eclipse.emf.ecore.EObject;
@@ -61,16 +59,22 @@ private EClassifier toEClassifier(JsonNode astJson) {
6159
} else if ("NamespaceReference".equals(type)) {
6260
type = "Namespace";
6361
} else if (type.equals("LiteralNumber")) {
64-
final String literalValue = astJson.get(LITERAL_CONST).asText();
65-
if (literalValue != null) {
66-
try {
67-
final BigDecimal bd = new BigDecimal(literalValue);
68-
bd.intValueExact();
69-
type = "LiteralInteger";
70-
} catch (final ArithmeticException e) {
62+
// There is a bug in AST provided by SysIDE, the literal drop the
63+
final var textValueNode = astJson.at("/$cstNode/text");
64+
if (textValueNode != null && textValueNode.asText() != null) {
65+
final String textValue = textValueNode.asText();
66+
if (textValue.contains(".") || textValue.contains("E") || textValue.contains("e")) {
67+
/*
68+
* <p>RealValue : Real = DECIMAL_VALUE? '.' ( DECIMAL_VALUE | EXPONENTIAL_VALUE ) | EXPONENTIAL_VALUE</p>
69+
* <p>EXPONENTIAL_VALUE = DECIMAL_VALUE ('e' | 'E') ('+' | '-')? DECIMAL_VALUE </p>
70+
*/
7171
type = "LiteralRational";
72+
} else {
73+
type = "LiteralInteger";
7274
}
7375
}
76+
77+
7478
// to remove when SysIDE will release a SysMLv2 2025-04 compliant version
7579
} else if ("FlowConnectionUsage".equals(type)) {
7680
type = "FlowUsage";

backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/SysMLElementSerializer.java

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import static org.eclipse.syson.sysml.textual.utils.SysMLRelationPredicates.IS_MEMBERSHIP;
1919
import static org.eclipse.syson.sysml.textual.utils.SysMLRelationPredicates.IS_METADATA_USAGE;
2020

21-
import java.lang.Class;
21+
import java.text.DecimalFormat;
22+
import java.text.DecimalFormatSymbols;
2223
import java.util.ArrayList;
2324
import java.util.Collection;
2425
import java.util.HashSet;
2526
import java.util.Iterator;
2627
import java.util.List;
2728
import java.util.ListIterator;
29+
import java.util.Locale;
2830
import java.util.Objects;
2931
import java.util.Optional;
3032
import java.util.Set;
@@ -36,8 +38,115 @@
3638
import org.eclipse.emf.common.util.EList;
3739
import org.eclipse.emf.ecore.EObject;
3840
import org.eclipse.emf.ecore.InternalEObject;
39-
import org.eclipse.syson.sysml.*;
41+
import org.eclipse.syson.sysml.AcceptActionUsage;
42+
import org.eclipse.syson.sysml.ActionDefinition;
43+
import org.eclipse.syson.sysml.ActionUsage;
44+
import org.eclipse.syson.sysml.ActorMembership;
45+
import org.eclipse.syson.sysml.AnalysisCaseUsage;
46+
import org.eclipse.syson.sysml.AssertConstraintUsage;
47+
import org.eclipse.syson.sysml.AttributeDefinition;
48+
import org.eclipse.syson.sysml.AttributeUsage;
49+
import org.eclipse.syson.sysml.CalculationDefinition;
50+
import org.eclipse.syson.sysml.Classifier;
51+
import org.eclipse.syson.sysml.CollectExpression;
52+
import org.eclipse.syson.sysml.Comment;
53+
import org.eclipse.syson.sysml.ConjugatedPortDefinition;
54+
import org.eclipse.syson.sysml.ConjugatedPortTyping;
55+
import org.eclipse.syson.sysml.ConstraintUsage;
56+
import org.eclipse.syson.sysml.ControlNode;
57+
import org.eclipse.syson.sysml.DecisionNode;
58+
import org.eclipse.syson.sysml.Definition;
59+
import org.eclipse.syson.sysml.Documentation;
60+
import org.eclipse.syson.sysml.Element;
61+
import org.eclipse.syson.sysml.EndFeatureMembership;
62+
import org.eclipse.syson.sysml.EnumerationDefinition;
63+
import org.eclipse.syson.sysml.EnumerationUsage;
64+
import org.eclipse.syson.sysml.Expose;
65+
import org.eclipse.syson.sysml.Expression;
66+
import org.eclipse.syson.sysml.Feature;
67+
import org.eclipse.syson.sysml.FeatureChainExpression;
68+
import org.eclipse.syson.sysml.FeatureChaining;
69+
import org.eclipse.syson.sysml.FeatureDirectionKind;
70+
import org.eclipse.syson.sysml.FeatureMembership;
71+
import org.eclipse.syson.sysml.FeatureReferenceExpression;
72+
import org.eclipse.syson.sysml.FeatureTyping;
73+
import org.eclipse.syson.sysml.FeatureValue;
74+
import org.eclipse.syson.sysml.FlowEnd;
75+
import org.eclipse.syson.sysml.FlowUsage;
76+
import org.eclipse.syson.sysml.ForkNode;
77+
import org.eclipse.syson.sysml.Import;
78+
import org.eclipse.syson.sysml.IncludeUseCaseUsage;
79+
import org.eclipse.syson.sysml.InterfaceDefinition;
80+
import org.eclipse.syson.sysml.InvocationExpression;
81+
import org.eclipse.syson.sysml.ItemDefinition;
82+
import org.eclipse.syson.sysml.ItemUsage;
83+
import org.eclipse.syson.sysml.JoinNode;
84+
import org.eclipse.syson.sysml.LibraryPackage;
85+
import org.eclipse.syson.sysml.LiteralBoolean;
86+
import org.eclipse.syson.sysml.LiteralExpression;
87+
import org.eclipse.syson.sysml.LiteralInfinity;
88+
import org.eclipse.syson.sysml.LiteralInteger;
89+
import org.eclipse.syson.sysml.LiteralRational;
90+
import org.eclipse.syson.sysml.LiteralString;
91+
import org.eclipse.syson.sysml.Membership;
92+
import org.eclipse.syson.sysml.MembershipExpose;
93+
import org.eclipse.syson.sysml.MembershipImport;
94+
import org.eclipse.syson.sysml.MergeNode;
95+
import org.eclipse.syson.sysml.Metaclass;
96+
import org.eclipse.syson.sysml.MetadataAccessExpression;
97+
import org.eclipse.syson.sysml.MetadataDefinition;
98+
import org.eclipse.syson.sysml.MetadataUsage;
99+
import org.eclipse.syson.sysml.MultiplicityRange;
100+
import org.eclipse.syson.sysml.Namespace;
101+
import org.eclipse.syson.sysml.NamespaceExpose;
102+
import org.eclipse.syson.sysml.NamespaceImport;
103+
import org.eclipse.syson.sysml.NullExpression;
104+
import org.eclipse.syson.sysml.ObjectiveMembership;
105+
import org.eclipse.syson.sysml.OccurrenceDefinition;
106+
import org.eclipse.syson.sysml.OccurrenceUsage;
107+
import org.eclipse.syson.sysml.OperatorExpression;
108+
import org.eclipse.syson.sysml.OwningMembership;
40109
import org.eclipse.syson.sysml.Package;
110+
import org.eclipse.syson.sysml.ParameterMembership;
111+
import org.eclipse.syson.sysml.PartDefinition;
112+
import org.eclipse.syson.sysml.PartUsage;
113+
import org.eclipse.syson.sysml.PayloadFeature;
114+
import org.eclipse.syson.sysml.PerformActionUsage;
115+
import org.eclipse.syson.sysml.PortDefinition;
116+
import org.eclipse.syson.sysml.PortUsage;
117+
import org.eclipse.syson.sysml.PortionKind;
118+
import org.eclipse.syson.sysml.Redefinition;
119+
import org.eclipse.syson.sysml.ReferenceSubsetting;
120+
import org.eclipse.syson.sysml.ReferenceUsage;
121+
import org.eclipse.syson.sysml.Relationship;
122+
import org.eclipse.syson.sysml.RenderingUsage;
123+
import org.eclipse.syson.sysml.RequirementDefinition;
124+
import org.eclipse.syson.sysml.RequirementUsage;
125+
import org.eclipse.syson.sysml.ReturnParameterMembership;
126+
import org.eclipse.syson.sysml.SatisfyRequirementUsage;
127+
import org.eclipse.syson.sysml.SelectExpression;
128+
import org.eclipse.syson.sysml.Specialization;
129+
import org.eclipse.syson.sysml.StakeholderMembership;
130+
import org.eclipse.syson.sysml.StateUsage;
131+
import org.eclipse.syson.sysml.Subclassification;
132+
import org.eclipse.syson.sysml.SubjectMembership;
133+
import org.eclipse.syson.sysml.Subsetting;
134+
import org.eclipse.syson.sysml.SuccessionAsUsage;
135+
import org.eclipse.syson.sysml.SysmlPackage;
136+
import org.eclipse.syson.sysml.TextualRepresentation;
137+
import org.eclipse.syson.sysml.TransitionFeatureKind;
138+
import org.eclipse.syson.sysml.TransitionFeatureMembership;
139+
import org.eclipse.syson.sysml.TransitionUsage;
140+
import org.eclipse.syson.sysml.TriggerInvocationExpression;
141+
import org.eclipse.syson.sysml.TriggerKind;
142+
import org.eclipse.syson.sysml.Type;
143+
import org.eclipse.syson.sysml.Usage;
144+
import org.eclipse.syson.sysml.UseCaseDefinition;
145+
import org.eclipse.syson.sysml.UseCaseUsage;
146+
import org.eclipse.syson.sysml.VerificationCaseUsage;
147+
import org.eclipse.syson.sysml.ViewUsage;
148+
import org.eclipse.syson.sysml.ViewpointDefinition;
149+
import org.eclipse.syson.sysml.VisibilityKind;
41150
import org.eclipse.syson.sysml.helper.EMFUtils;
42151
import org.eclipse.syson.sysml.helper.LabelConstants;
43152
import org.eclipse.syson.sysml.textual.utils.Appender;
@@ -54,6 +163,10 @@
54163
*/
55164
public class SysMLElementSerializer extends SysmlSwitch<String> {
56165

166+
private static final DecimalFormat SIMPLE_LITERAL_RATIONAL_FORMAT = new DecimalFormat("0.0#", new DecimalFormatSymbols(Locale.US));
167+
168+
private static final DecimalFormat SCIENTIFIC_LITERAL_RATIONAL_FORMAT = new DecimalFormat("0.0#####E0", new DecimalFormatSymbols(Locale.US));
169+
57170
private final String lineSeparator;
58171

59172
private final String indentation;
@@ -511,10 +624,18 @@ public String caseLiteralInteger(LiteralInteger literal) {
511624
@Override
512625
public String caseLiteralRational(LiteralRational literal) {
513626
Appender builder = this.newAppender();
514-
builder.append(String.valueOf(literal.getValue()));
627+
builder.append(this.toPreciseReal(literal.getValue()));
515628
return builder.toString();
516629
}
517630

631+
private String toPreciseReal(double value) {
632+
if (Math.abs(value) >= 1e-3 && Math.abs(value) < 1e6) {
633+
return SIMPLE_LITERAL_RATIONAL_FORMAT.format(value);
634+
} else {
635+
return SCIENTIFIC_LITERAL_RATIONAL_FORMAT.format(value);
636+
}
637+
}
638+
518639
@Override
519640
public String caseLiteralString(LiteralString literal) {
520641
Appender builder = this.newAppender();

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,43 @@ metadata def MP :> SemanticMetadata {
6262
}
6363
```
6464

65+
- Fix an issue during import and export of rational numbers.
66+
The following model is now properly imported:
67+
68+
```
69+
package Occurrences {
70+
private import ScalarValues::*;
71+
occurrence def Occurrence1 {
72+
attribute f : Real = 1.0;
73+
attribute g = 3.14;
74+
attribute h = 2.5E-10;
75+
attribute i = .5;
76+
attribute j = 1E+3;
77+
}
78+
enum def MyEnum {
79+
enum1;
80+
}
81+
}
82+
```
83+
84+
Here is the textual export result:
85+
86+
```
87+
package Occurrences {
88+
private import ScalarValues::*;
89+
occurrence def Occurrence1 {
90+
attribute f : Real = 1.0;
91+
attribute g = 3.14;
92+
attribute h = 2.5E-10;
93+
attribute i = 0.5;
94+
attribute j = 1000.0;
95+
}
96+
enum def MyEnum {
97+
enum1;
98+
}
99+
}
100+
```
101+
65102
== Improvements
66103

67104
- In diagrams, when using direct edit on graphical elements, it's now possible to use _short names_ in the qualified names.

0 commit comments

Comments
 (0)