Skip to content

Commit 4c49c34

Browse files
authored
Merge pull request #778 from bci-oss/770-code-generation-not-incorporating-optional-property
Fix Optional.of for samm:optional true; property
2 parents b3e1ac9 + 15efc62 commit 4c49c34

File tree

14 files changed

+212
-56
lines changed

14 files changed

+212
-56
lines changed

core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ class RdfModelCreatorVisitorTest {
5656
"ASPECT_WITH_TIME_SERIES",
5757
"ASPECT_WITH_QUANTITY",
5858
"ASPECT_WITH_NAMESPACE_DESCRIPTION",
59-
"ASPECT_WITH_ANY_VALUE_DECLARATIONS"
59+
"ASPECT_WITH_ANY_VALUE_DECLARATIONS",
60+
"ASPECT_WITH_OPTIONAL_PROPERTIES_AND_ENTITY_WITH_SEPARATE_FILES"
6061
} )
6162
void testRdfModelCreatorVisitor( final TestAspect testAspect ) {
6263
final AspectModel aspectModel = TestResources.load( testAspect );

core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/metamodel/UnitGenerationTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24-
public class UnitGenerationTest {
24+
class UnitGenerationTest {
2525
@Test
26-
public void testUnitFromName() {
26+
void testUnitFromName() {
2727
final Optional<Unit> optionalUnit = Units.fromName( "degreeCelsius" );
2828
final Unit unit = optionalUnit.get();
2929
assertThat( unit )
@@ -33,19 +33,19 @@ public void testUnitFromName() {
3333
}
3434

3535
@Test
36-
public void testUnitWithoutReferenceUnit() {
36+
void testUnitWithoutReferenceUnit() {
3737
final Unit ampere = Units.fromName( "ampere" ).get();
3838
assertThat( ampere ).hasNoReferenceUnit();
3939
}
4040

4141
@Test
42-
public void testUnitFromcode() {
42+
void testUnitFromcode() {
4343
final Unit unit = Units.fromCode( "A97" ).get();
4444
assertThat( unit ).isEqualTo( Units.fromName( "hectopascal" ).get() );
4545
}
4646

4747
@Test
48-
public void testUnitFromSymbol() {
48+
void testUnitFromSymbol() {
4949
// Unit with unique symbol
5050
assertThat( Units.fromSymbol( "kg" ) ).containsExactly( Units.fromName( "kilogram" ).get() );
5151

@@ -54,7 +54,7 @@ public void testUnitFromSymbol() {
5454
}
5555

5656
@Test
57-
public void testUnitsWithQuantityKind() {
57+
void testUnitsWithQuantityKind() {
5858
final Set<Unit> units = Units.unitsWithQuantityKind( QuantityKinds.DISTANCE );
5959
assertThat( units ).containsAnyOf( Units.fromName( "nanometre" ).get(), Units.fromName( "metre" ).get(),
6060
Units.fromName( "kilometre" ).get(), Units.fromName( "foot" ).get(), Units.fromName( "lightYear" ).get() );
@@ -63,12 +63,12 @@ public void testUnitsWithQuantityKind() {
6363
}
6464

6565
@Test
66-
public void testGeneratedQuantityKinds() {
66+
void testGeneratedQuantityKinds() {
6767
assertThat( QuantityKinds.values().length ).isGreaterThanOrEqualTo( 80 );
6868
}
6969

7070
@Test
71-
public void testQuantityKindFromName() {
71+
void testQuantityKindFromName() {
7272
final QuantityKind quantityKind = QuantityKinds.fromName( "distance" ).get();
7373
assertThat( quantityKind ).hasName( "distance" ).hasLabel( "distance" );
7474
}

core/esmf-aspect-model-document-generators/src/test/java/org/eclipse/esmf/aspectmodel/generator/json/AspectModelJsonPayloadGeneratorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ static void setup() {
147147
@ParameterizedTest
148148
@EnumSource( value = TestAspect.class, mode = EnumSource.Mode.EXCLUDE, names = {
149149
"MODEL_WITH_BROKEN_CYCLES",
150-
"ASPECT_WITH_MULTIPLE_ENTITIES_SAME_EXTEND"
150+
"ASPECT_WITH_MULTIPLE_ENTITIES_SAME_EXTEND",
151+
"ASPECT_WITH_OPTIONAL_PROPERTIES_AND_ENTITY_WITH_SEPARATE_FILES"
151152
} )
152153
void testDeserializationForGeneratedJson( final TestAspect testAspect ) {
153154
final Aspect aspect = TestResources.load( testAspect ).aspect();

core/esmf-aspect-model-jackson/src/test/java/org/eclipse/esmf/aspectmodel/jackson/AspectModelJacksonModuleTest.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@
5757
import io.vavr.Tuple2;
5858
import org.junit.jupiter.api.Test;
5959

60-
public class AspectModelJacksonModuleTest {
60+
class AspectModelJacksonModuleTest {
6161
private static final String PACKAGE = "org.eclipse.esmf.test";
6262

6363
@Test
64-
public void testAspectWithMultiLanguageText() throws Exception {
64+
void testAspectWithMultiLanguageText() throws Exception {
6565
final Object instance = generateInstance( TestAspect.ASPECT_WITH_MULTI_LANGUAGE_TEXT );
6666
final Class<?> clazz = instance.getClass();
6767
final LangString prop = getValue( clazz, instance, "prop", LangString.class );
@@ -70,7 +70,7 @@ public void testAspectWithMultiLanguageText() throws Exception {
7070
}
7171

7272
@Test
73-
public void testAspectWithSimpleTypes() throws Exception {
73+
void testAspectWithSimpleTypes() throws Exception {
7474
final Object instance = generateInstance( TestAspect.ASPECT_WITH_SIMPLE_TYPES );
7575
final Class<?> clazz = instance.getClass();
7676

@@ -93,7 +93,7 @@ public void testAspectWithSimpleTypes() throws Exception {
9393
}
9494

9595
@Test
96-
public void testAspectWithCollection() throws Exception {
96+
void testAspectWithCollection() throws Exception {
9797
final Object instance = generateInstance( TestAspect.ASPECT_WITH_COLLECTIONS );
9898
final Class<?> clazz = instance.getClass();
9999

@@ -109,7 +109,7 @@ public void testAspectWithCollection() throws Exception {
109109
}
110110

111111
@Test
112-
public void testAspectWithEntity() throws Exception {
112+
void testAspectWithEntity() throws Exception {
113113
final Object instance = generateInstance( TestAspect.ASPECT_WITH_SIMPLE_ENTITY );
114114
final Class<?> clazz = instance.getClass();
115115

@@ -123,7 +123,7 @@ public void testAspectWithEntity() throws Exception {
123123
}
124124

125125
@Test
126-
public void testAspectWithOptionalProperties() throws Exception {
126+
void testAspectWithOptionalProperties() throws Exception {
127127
final Object instance = generateInstance( TestAspect.ASPECT_WITH_OPTIONAL_PROPERTIES );
128128
final Class<?> clazz = instance.getClass();
129129

@@ -137,7 +137,7 @@ public void testAspectWithOptionalProperties() throws Exception {
137137
}
138138

139139
@Test
140-
public void testAspectWithStructuredValue() throws Exception {
140+
void testAspectWithStructuredValue() throws Exception {
141141
final Object instance = generateInstance( TestAspect.ASPECT_WITH_NUMERIC_STRUCTURED_VALUE );
142142
final Class<?> clazz = instance.getClass();
143143

@@ -146,18 +146,21 @@ public void testAspectWithStructuredValue() throws Exception {
146146
assertThat( date.getMonth() ).isEqualTo( 1 );
147147
assertThat( date.getDay() ).isEqualTo( 20 );
148148

149-
final Long year = getValue( clazz, instance, "year", Long.class );
150-
assertThat( year ).isEqualTo( 2020L );
149+
final Optional<Long> yearOptional = getValue( clazz, instance, "year", Optional.class );
150+
assertThat( yearOptional ).isPresent();
151+
assertThat( yearOptional.get() ).isEqualTo( 2020L );
151152

152-
final Long month = getValue( clazz, instance, "month", Long.class );
153-
assertThat( month ).isEqualTo( 1 );
153+
final Optional<Long> monthOptional = getValue( clazz, instance, "month", Optional.class );
154+
assertThat( monthOptional ).isPresent();
155+
assertThat( monthOptional.get() ).isEqualTo( 1L );
154156

155-
final Long day = getValue( clazz, instance, "day", Long.class );
156-
assertThat( day ).isEqualTo( 20 );
157+
final Optional<Long> dayOptional = getValue( clazz, instance, "day", Optional.class );
158+
assertThat( dayOptional ).isPresent();
159+
assertThat( dayOptional.get() ).isEqualTo( 20L );
157160
}
158161

159162
@Test
160-
public void testAspectWithEnumeration() throws Exception {
163+
void testAspectWithEnumeration() throws Exception {
161164
final Object instance = generateInstance( TestAspect.ASPECT_WITH_STRING_ENUMERATION );
162165
final Class<?> clazz = instance.getClass();
163166

@@ -172,7 +175,7 @@ public void testAspectWithEnumeration() throws Exception {
172175
}
173176

174177
@Test
175-
public void testAspectWithEntityEnumeration() throws Exception {
178+
void testAspectWithEntityEnumeration() throws Exception {
176179
final Object instance = generateInstance(
177180
Tuple.of( TestAspect.ASPECT_WITH_ENTITY_ENUMERATION_WITH_NOT_EXISTING_ENUM, "AspectWithEntityEnumeration" ) );
178181
final Class<?> clazz = instance.getClass();
@@ -191,14 +194,14 @@ public void testAspectWithEntityEnumeration() throws Exception {
191194
}
192195

193196
@Test
194-
public void testAspectWithEntityEnumerationWithNotExistingEnum() {
197+
void testAspectWithEntityEnumerationWithNotExistingEnum() {
195198
assertThatExceptionOfType( EnumAttributeNotFoundException.class ).isThrownBy( () ->
196199
generateInstance( TestAspect.ASPECT_WITH_ENTITY_ENUMERATION_WITH_NOT_EXISTING_ENUM ) )
197200
.withMessageContainingAll( "Tried to parse value", "but there is no enum field like that" );
198201
}
199202

200203
@Test
201-
public void testAspectWithEntityEnumerationAndNotInPayloadProperties() throws Exception {
204+
void testAspectWithEntityEnumerationAndNotInPayloadProperties() throws Exception {
202205
final Object instance = generateInstance( TestAspect.ASPECT_WITH_ENTITY_ENUMERATION_AND_NOT_IN_PAYLOAD_PROPERTIES );
203206
final Class<?> clazz = instance.getClass();
204207
final Field enumerationField = clazz.getDeclaredField( "systemState" );
@@ -216,7 +219,7 @@ public void testAspectWithEntityEnumerationAndNotInPayloadProperties() throws Ex
216219
}
217220

218221
@Test
219-
public void testAspectWithEitherWithComplexTypes() throws Exception {
222+
void testAspectWithEitherWithComplexTypes() throws Exception {
220223
final Object instance = generateInstance( TestAspect.ASPECT_WITH_EITHER_WITH_COMPLEX_TYPES );
221224
final Class<?> clazz = instance.getClass();
222225
final Field testProperty = clazz.getDeclaredField( "testProperty" );

core/esmf-aspect-model-java-generator/src/main/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtil.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import java.util.Set;
2525
import java.util.function.Predicate;
2626
import java.util.function.Supplier;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
2729
import java.util.stream.Collectors;
2830
import java.util.stream.IntStream;
2931
import java.util.stream.Stream;
30-
32+
import javax.xml.datatype.DatatypeConstants;
3133
import org.eclipse.esmf.aspectmodel.VersionInfo;
3234
import org.eclipse.esmf.aspectmodel.java.exception.CodeGenerationException;
3335
import org.eclipse.esmf.aspectmodel.visitor.AspectStreamTraversalVisitor;
@@ -473,7 +475,8 @@ public static String generateInitializer( final Property property, final String
473475
final Resource typeResource = ResourceFactory.createResource( type.getUrn() );
474476
final Class<?> result = SammXsdType.getJavaTypeForMetaModelType( typeResource );
475477
codeGenerationConfig.importTracker().importExplicit( result );
476-
return valueInitializer.apply( typeResource, value );
478+
final String initializer = valueInitializer.apply( typeResource, value );
479+
return optionalExpression( initializer, codeGenerationConfig );
477480
} ).orElseThrow( () -> new CodeGenerationException(
478481
"The Either Characteristic is not allowed for Properties used as elements in a StructuredValue" ) );
479482
}
@@ -668,10 +671,46 @@ public static String getterName( final Property property ) {
668671
.filter( Type::isScalar )
669672
.map( type -> XSD.xboolean.getURI().equals( type.getUrn() ) )
670673
.orElse( false );
671-
return ( isBooleanType ? "is" : "get" ) + StringUtils.capitalize( property.getPayloadName() );
674+
return (isBooleanType ? "is" : "get") + StringUtils.capitalize( property.getPayloadName() );
672675
}
673676

674677
public static String codeGeneratorName() {
675678
return "esmf-sdk " + VersionInfo.ESMF_SDK_VERSION;
676679
}
680+
681+
public static String optionalExpression(
682+
final String expression,
683+
final JavaCodeGenerationConfig codeGenerationConfig
684+
) {
685+
codeGenerationConfig.importTracker().importExplicit( Optional.class );
686+
codeGenerationConfig.importTracker().importExplicit( DatatypeConstants.class );
687+
688+
// Enhanced pattern to match both Integer and Long valueOf calls
689+
final Pattern pattern = java.util.regex.Pattern.compile(
690+
"(Integer|Long)\\s*\\.\\s*valueOf\\s*\\(\\s*matcher\\s*\\.\\s*group\\s*\\(\\s*(\\d+)\\s*\\)\\s*\\)"
691+
);
692+
Matcher matcher = pattern.matcher( expression );
693+
matcher.reset();
694+
boolean hasMatch = matcher.find();
695+
696+
if ( hasMatch ) {
697+
final String numberType = matcher.group( 1 ).trim(); // "Integer" or "Long"
698+
final String groupNum = matcher.group( 2 ).trim(); // Group number
699+
final String target = matcher.group( 0 ); // The entire matched string
700+
final String modifiedExpression = expression.replace( target, "v" );
701+
702+
return "Optional.ofNullable(matcher.group(" + groupNum + "))"
703+
+ ".filter(v -> !v.isEmpty())"
704+
+ ".map(v -> " + numberType + ".valueOf(v))"
705+
+ ".map(v -> "
706+
+ modifiedExpression
707+
+ ")";
708+
}
709+
710+
// If no specific pattern matched, wrap the entire expression in Optional.of()
711+
// This handles cases where the expression doesn't follow the matcher.group pattern
712+
return "Optional.ofNullable("
713+
+ expression
714+
+ ")";
715+
}
677716
}

core/esmf-aspect-model-java-generator/src/main/java/org/eclipse/esmf/aspectmodel/java/StructuredValuePropertiesDeconstructor.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.eclipse.esmf.aspectmodel.java;
1515

1616
import java.util.List;
17+
import java.util.Optional;
1718
import java.util.stream.Collectors;
1819
import java.util.stream.Stream;
1920

@@ -22,6 +23,7 @@
2223
import org.eclipse.esmf.metamodel.HasProperties;
2324
import org.eclipse.esmf.metamodel.Property;
2425
import org.eclipse.esmf.metamodel.characteristic.StructuredValue;
26+
import org.eclipse.esmf.metamodel.impl.DefaultProperty;
2527

2628
import org.apache.commons.lang3.StringUtils;
2729

@@ -116,6 +118,18 @@ private List<Property> getAllProperties( final List<DeconstructionSet> deconstru
116118
return deconstructionSets.stream()
117119
.map( DeconstructionSet::elementProperties )
118120
.flatMap( List::stream )
121+
.map( property -> (Property) new DefaultProperty(
122+
MetaModelBaseAttributes.builder()
123+
.fromModelElement( property )
124+
.build(),
125+
property.getCharacteristic(),
126+
property.getExampleValue(),
127+
true, // making it optional
128+
property.isNotInPayload(),
129+
Optional.of( property.getPayloadName() ),
130+
property.isAbstract(),
131+
property.getExtends()
132+
) )
119133
.toList();
120134
}
121135

core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaGeneratorTest.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Optional;
3030
import java.util.Set;
3131
import java.util.regex.Pattern;
32+
3233
import javax.xml.datatype.Duration;
3334
import javax.xml.datatype.XMLGregorianCalendar;
3435

@@ -63,7 +64,7 @@
6364
import org.junit.jupiter.params.ParameterizedTest;
6465
import org.junit.jupiter.params.provider.EnumSource;
6566

66-
public class AspectModelJavaGeneratorTest {
67+
class AspectModelJavaGeneratorTest {
6768
private Collection<JavaGenerator> getGenerators( final TestAspect testAspect, final JavaCodeGenerationConfig config ) {
6869
final Aspect aspect = TestResources.load( testAspect ).aspect();
6970
return List.of( new AspectModelJavaGenerator( aspect, config ) );
@@ -240,6 +241,28 @@ void testGenerateAspectModelWithOptionalProperties() throws IOException {
240241
assertConstructor( result, "AspectWithOptionalProperties", expectedFieldsForAspectClass );
241242
}
242243

244+
@Test
245+
void testGenerateAspectWithOptionalPropertyAndEntityWithSeparateFiles() throws IOException {
246+
final ImmutableMap<String, Object> expectedFieldsForAspectClass = ImmutableMap.<String, Object> builder()
247+
.put( "productionPeriod", "Optional<ProductionPeriodEntity>" )
248+
.build();
249+
250+
final TestAspect aspect = TestAspect.ASPECT_WITH_OPTIONAL_PROPERTIES_AND_ENTITY_WITH_SEPARATE_FILES;
251+
final GenerationResult result = TestContext.generateAspectCode().apply( getGenerators( aspect ) );
252+
253+
result.assertNumberOfFiles( 2 );
254+
result.assertFields( "AspectWithOptionalPropertiesAndEntityWithSeparateFiles", expectedFieldsForAspectClass, new HashMap<>() );
255+
assertConstructor( result, "AspectWithOptionalPropertiesAndEntityWithSeparateFiles", expectedFieldsForAspectClass );
256+
257+
CompilationUnit unit = result.compilationUnits.get( "ProductionPeriodEntity" );
258+
ConstructorDeclaration constructor = unit.findFirst( ConstructorDeclaration.class )
259+
.orElseThrow( () -> new AssertionError( "Constructor not found in ProductionPeriodEntity" ) );
260+
String constructorBody = constructor.getBody().toString();
261+
assertThat( constructorBody ).contains( "Optional.ofNullable(" );
262+
assertThat( constructorBody ).contains( ".filter(v -> !v.isEmpty())" );
263+
assertThat( constructorBody ).contains( "_datatypeFactory.newXMLGregorianCalendarDate(" );
264+
}
265+
243266
@Test
244267
void testGenerateAspectModelWithCurie() throws IOException {
245268
final ImmutableMap<String, Object> expectedFieldsForAspectClass = ImmutableMap.<String, Object> builder()
@@ -601,9 +624,9 @@ void testGenerateAspectModelMultipleEntitiesOnMultipleLevelsWithoutJacksonAnnota
601624
void testGenerateAspectWithStructuredValue() throws IOException {
602625
final ImmutableMap<String, Object> expectedFieldsForAspectClass = ImmutableMap.<String, Object> builder()
603626
.put( "date", XMLGregorianCalendar.class )
604-
.put( "year", Long.class )
605-
.put( "month", Long.class )
606-
.put( "day", Long.class )
627+
.put( "year", new TypeToken<Optional<Long>>(){}.getType() )
628+
.put( "month", new TypeToken<Optional<Long>>(){}.getType() )
629+
.put( "day", new TypeToken<Optional<Long>>(){}.getType() )
607630
.build();
608631

609632
final ImmutableMap<String, Object> expectedConstructorArguments = ImmutableMap.<String, Object> builder()

core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtilTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.jupiter.params.provider.Arguments;
3030
import org.junit.jupiter.params.provider.MethodSource;
3131

32-
public class AspectModelJavaUtilTest extends PropertyBasedTest {
32+
class AspectModelJavaUtilTest extends PropertyBasedTest {
3333
private boolean isValidJavaIdentifier( final String value ) {
3434
if ( value == null || value.isEmpty() ) {
3535
return false;
@@ -45,7 +45,7 @@ private boolean isValidJavaIdentifier( final String value ) {
4545
}
4646

4747
@Property
48-
public boolean generatedEnumKeysAreValidJavaIdentifiers( @ForAll( "anyValidTypeValuePair" ) final Tuple.Tuple2<Type, Value> tuple ) {
48+
boolean generatedEnumKeysAreValidJavaIdentifiers( @ForAll( "anyValidTypeValuePair" ) final Tuple.Tuple2<Type, Value> tuple ) {
4949
final String result = AspectModelJavaUtil.generateEnumKey( tuple.get2() );
5050
return isValidJavaIdentifier( result );
5151
}

0 commit comments

Comments
 (0)