Skip to content

Commit 90968ac

Browse files
committed
new auto-gen activity value mappers to use activity types as parameters.
1 parent a636850 commit 90968ac

File tree

12 files changed

+425
-73
lines changed

12 files changed

+425
-73
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package gov.nasa.jpl.aerie.banananation.activities;
2+
3+
import gov.nasa.jpl.aerie.banananation.Mission;
4+
import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType;
5+
import gov.nasa.jpl.aerie.merlin.framework.annotations.AutoValueMapper;
6+
import gov.nasa.jpl.aerie.merlin.framework.annotations.Export;
7+
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
8+
9+
import java.util.List;
10+
import java.util.Optional;
11+
12+
import static gov.nasa.jpl.aerie.banananation.generated.ActivityActions.call;
13+
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
14+
15+
/**
16+
* RussianNestingBanana nests activity types as parameters inside an activity
17+
*
18+
* This activity tests the use of activity types as parameters and within compound type parameters. There are a few use cases:
19+
* 1. Basic activity type parameter which is passed through and called without parent level modeling
20+
* 2. List of activity types which are just iterated through and called
21+
* 3. Using a parent level parameter to override information in the call to a child from an activity type parameter
22+
*
23+
* @subsystem fruit
24+
* @contact John Doe
25+
*/
26+
@ActivityType("RussianNestingBanana")
27+
public final class RussianNestingBanana {
28+
29+
/** Record encapsulating an activity type **/
30+
@AutoValueMapper.Record
31+
public record pickBananaWithId(
32+
int id,
33+
PickBananaActivity pickBananaActivity
34+
) {}
35+
36+
/** Record type parameter encapsulating an activity type **/
37+
@Export.Parameter
38+
public pickBananaWithId pickBananaActivityRecord;
39+
40+
/** Parent level override parameter, in this case to override call to peel banana
41+
* found in the pickBannaActivityRecord parameter **/
42+
@Export.Parameter
43+
public int pickBananaQuantityOverride = 0;
44+
45+
/** List of activity type parameter example **/
46+
@Export.Parameter
47+
public List<BiteBananaActivity> biteBananaActivity;
48+
49+
/** Vanilla activity type parameter **/
50+
@Export.Parameter
51+
public PeelBananaActivity peelBananaActivity;
52+
53+
54+
@ActivityType.EffectModel
55+
public void run(final Mission mission) {
56+
// if the pickBananaQuantityOverride is preset use that integer instead of the pickBananaActivityRecord
57+
if(pickBananaQuantityOverride != 0) {
58+
PickBananaActivity pickBananaActivity = pickBananaActivityRecord.pickBananaActivity;
59+
pickBananaActivity.quantity = pickBananaQuantityOverride;
60+
call(mission, pickBananaActivity);
61+
} else { // else use the record type parameter supplied
62+
call(mission, pickBananaActivityRecord.pickBananaActivity());
63+
}
64+
// call a bite banana for each element in the list of biteBanana activities
65+
for (final var bite : biteBananaActivity) {
66+
call(mission, bite);
67+
delay(Duration.of(30, Duration.MINUTE));
68+
}
69+
// call peel banana activity
70+
call(mission, peelBananaActivity);
71+
}
72+
}

examples/banananation/src/main/java/gov/nasa/jpl/aerie/banananation/package-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
@WithActivityType(ControllableDurationActivity.class)
2727
@WithActivityType(RipenBananaActivity.class)
2828
@WithActivityType(ExceptionActivity.class)
29-
29+
@WithActivityType(RussianNestingBanana.class)
3030
package gov.nasa.jpl.aerie.banananation;
3131

3232
import gov.nasa.jpl.aerie.banananation.activities.BakeBananaBreadActivity;
@@ -46,6 +46,7 @@
4646
import gov.nasa.jpl.aerie.banananation.activities.PeelBananaActivity;
4747
import gov.nasa.jpl.aerie.banananation.activities.PickBananaActivity;
4848
import gov.nasa.jpl.aerie.banananation.activities.RipenBananaActivity;
49+
import gov.nasa.jpl.aerie.banananation.activities.RussianNestingBanana;
4950
import gov.nasa.jpl.aerie.banananation.activities.ThrowBananaActivity;
5051
import gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers;
5152
import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package gov.nasa.jpl.aerie.banananation.activities;
2+
3+
import gov.nasa.jpl.aerie.banananation.Configuration;
4+
import gov.nasa.jpl.aerie.banananation.Mission;
5+
import gov.nasa.jpl.aerie.banananation.SimulationUtility;
6+
import gov.nasa.jpl.aerie.banananation.generated.GeneratedModelType;
7+
import gov.nasa.jpl.aerie.banananation.generated.activities.ParameterTestActivityMapper;
8+
import gov.nasa.jpl.aerie.banananation.generated.activities.RussianNestingBananaMapper;
9+
import gov.nasa.jpl.aerie.contrib.serialization.mappers.DurationValueMapper;
10+
import gov.nasa.jpl.aerie.merlin.driver.DirectiveTypeRegistry;
11+
import gov.nasa.jpl.aerie.merlin.driver.MissionModelBuilder;
12+
import gov.nasa.jpl.aerie.merlin.driver.SerializedActivity;
13+
import gov.nasa.jpl.aerie.merlin.framework.Registrar;
14+
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
15+
import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException;
16+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
17+
import org.apache.commons.lang3.tuple.Pair;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.TestInstance;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import java.nio.file.Path;
23+
import java.time.Instant;
24+
import java.util.HashMap;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
import static gov.nasa.jpl.aerie.banananation.generated.ActivityActions.call;
29+
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MILLISECONDS;
30+
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.SECONDS;
31+
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.duration;
32+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
35+
@ExtendWith(MerlinExtension.class)
36+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
37+
public class RussianNestingActivityTest {
38+
private final RussianNestingBananaMapper mapper;
39+
40+
public RussianNestingActivityTest() {
41+
this.mapper = new RussianNestingBananaMapper();
42+
}
43+
44+
@Test
45+
public void testDefaultSimulationDoesNotThrow() {
46+
final var schedule = SimulationUtility.buildSchedule(
47+
Pair.of(
48+
duration(1100, MILLISECONDS),
49+
new SerializedActivity("RussianNestingBanana",
50+
Map.of("pickBananaActivityRecord",
51+
SerializedValue.of(Map.of("id", SerializedValue.of(2),
52+
"pickBananaActivity", SerializedValue.of(Map.of("quantity", SerializedValue.of(10)))))
53+
, "pickBananaQuantityOverride", SerializedValue.of(0),
54+
"biteBananaActivity", SerializedValue.of(List.of()),
55+
"peelBananaActivity", SerializedValue.of(Map.of("peelDirection", SerializedValue.of("fromStem")))))));
56+
57+
final var simulationDuration = duration(5, SECONDS);
58+
59+
final var simulationResults = SimulationUtility.simulate(schedule, simulationDuration);
60+
61+
System.out.println(simulationResults.discreteProfiles);
62+
System.out.println(simulationResults.realProfiles);
63+
}
64+
65+
}

merlin-framework-processor/src/main/java/gov/nasa/jpl/aerie/merlin/processor/AutoValueMappers.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import gov.nasa.jpl.aerie.contrib.serialization.mappers.RecordValueMapper;
1414
import gov.nasa.jpl.aerie.merlin.framework.Result;
1515
import gov.nasa.jpl.aerie.merlin.framework.ValueMapper;
16+
import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType;
1617
import gov.nasa.jpl.aerie.merlin.framework.annotations.AutoValueMapper;
1718
import gov.nasa.jpl.aerie.merlin.processor.metamodel.MissionModelRecord;
1819
import gov.nasa.jpl.aerie.merlin.processor.metamodel.TypeRule;
@@ -61,6 +62,24 @@ static TypeRule recordTypeRule(final Element autoValueMapperElement, final Class
6162
ClassName.get((TypeElement) autoValueMapperElement).canonicalName().replace(".", "_"));
6263
}
6364

65+
static TypeRule activityTypeRule(final Element activityTypeElement, final ClassName generatedClassName) throws InvalidMissionModelException {
66+
if (!(activityTypeElement.getKind().equals(ElementKind.CLASS) || activityTypeElement.getKind().equals(ElementKind.RECORD))) { //todo: check if activities are constrained to class and record
67+
throw new InvalidMissionModelException(
68+
"@%s is only allowed on classes and records".formatted(
69+
ActivityType.class.getSimpleName()),
70+
activityTypeElement);
71+
}
72+
73+
return new TypeRule(
74+
new TypePattern.ClassPattern(
75+
ClassName.get(ValueMapper.class),
76+
List.of(TypePattern.from(activityTypeElement.asType()))),
77+
Set.of(),
78+
List.of(),
79+
generatedClassName,
80+
ClassName.get((TypeElement) activityTypeElement).canonicalName().replace("activities", "generated_activitiesValueMappers").replace(".", "_") + "ValueMapper");
81+
}
82+
6483
static TypeRule annotationTypeRule(final Element autoValueMapperElement, final ClassName generatedClassName) throws InvalidMissionModelException {
6584
if (!autoValueMapperElement.getKind().equals(ElementKind.ANNOTATION_TYPE)) {
6685
throw new InvalidMissionModelException(

merlin-framework-processor/src/main/java/gov/nasa/jpl/aerie/merlin/processor/MissionModelParser.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
import gov.nasa.jpl.aerie.merlin.framework.annotations.Export;
1010
import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel;
1111
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ActivityTypeRecord;
12+
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ActivityValueMapperRecord;
1213
import gov.nasa.jpl.aerie.merlin.processor.metamodel.InputTypeRecord;
1314
import gov.nasa.jpl.aerie.merlin.processor.metamodel.EffectModelRecord;
1415
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ExportDefaultsStyle;
15-
import gov.nasa.jpl.aerie.merlin.processor.metamodel.MapperRecord;
16+
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ActivityMapperRecord;
1617
import gov.nasa.jpl.aerie.merlin.processor.metamodel.MissionModelRecord;
1718
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ParameterRecord;
1819
import gov.nasa.jpl.aerie.merlin.processor.metamodel.ParameterValidationRecord;
@@ -183,9 +184,10 @@ private Optional<InputTypeRecord> getMissionModelConfigurationType(final Package
183184
final var name = declaration.getSimpleName().toString();
184185
final var parameters = getExportParameters(declaration);
185186
final var validations = this.getExportValidations(declaration, parameters);
186-
final var mapper = getExportMapper(missionModelElement, declaration);
187+
final var activityMapper = getExportActivityMapper(missionModelElement, declaration);
188+
final var valueMapper = getExportValueMapper(missionModelElement, declaration);
187189
final var defaultsStyle = getExportDefaultsStyle(declaration);
188-
return Optional.of(new InputTypeRecord(name, declaration, parameters, validations, mapper, defaultsStyle));
190+
return Optional.of(new InputTypeRecord(name, declaration, parameters, validations, activityMapper, valueMapper, defaultsStyle));
189191
}
190192

191193
private List<TypeElement> getMissionModelMapperClasses(final PackageElement missionModelElement)
@@ -381,7 +383,8 @@ private ActivityTypeRecord parseActivityType(final PackageElement missionModelEl
381383
{
382384
final var fullyQualifiedClassName = activityTypeElement.getQualifiedName();
383385
final var name = this.getActivityTypeName(activityTypeElement);
384-
final var mapper = this.getExportMapper(missionModelElement, activityTypeElement);
386+
final var activityMapper = getExportActivityMapper(missionModelElement, activityTypeElement);
387+
final var valueMapper = getExportValueMapper(missionModelElement, activityTypeElement);
385388
final var parameters = this.getExportParameters(activityTypeElement);
386389
final var validations = this.getExportValidations(activityTypeElement, parameters);
387390
final var effectModel = this.getActivityEffectModel(activityTypeElement);
@@ -405,7 +408,7 @@ class (old-style) or as a record (new-style) by determining
405408
return new ActivityTypeRecord(
406409
fullyQualifiedClassName.toString(),
407410
name,
408-
new InputTypeRecord(name, activityTypeElement, parameters, validations, mapper, defaultsStyle),
411+
new InputTypeRecord(name, activityTypeElement, parameters, validations, activityMapper, valueMapper, defaultsStyle),
409412
effectModel);
410413
}
411414

@@ -471,12 +474,12 @@ private String getActivityTypeName(final TypeElement activityTypeElement)
471474
return (String) nameAttribute.getValue();
472475
}
473476

474-
private MapperRecord getExportMapper(final PackageElement missionModelElement, final TypeElement exportTypeElement)
477+
private ActivityMapperRecord getExportActivityMapper(final PackageElement missionModelElement, final TypeElement exportTypeElement)
475478
throws InvalidMissionModelException
476479
{
477480
final var annotationMirror = this.getAnnotationMirrorByType(exportTypeElement, ActivityType.WithMapper.class);
478481
if (annotationMirror.isEmpty()) {
479-
return MapperRecord.generatedFor(
482+
return ActivityMapperRecord.generatedFor(
480483
ClassName.get(exportTypeElement),
481484
missionModelElement);
482485
}
@@ -488,7 +491,27 @@ private MapperRecord getExportMapper(final PackageElement missionModelElement, f
488491
annotationMirror.get()))
489492
.getValue();
490493

491-
return MapperRecord.custom(
494+
return ActivityMapperRecord.custom(
495+
ClassName.get((TypeElement) mapperType.asElement()));
496+
}
497+
private ActivityValueMapperRecord getExportValueMapper(final PackageElement missionModelElement, final TypeElement exportTypeElement)
498+
throws InvalidMissionModelException
499+
{
500+
final var annotationMirror = this.getAnnotationMirrorByType(exportTypeElement, ActivityType.WithMapper.class);
501+
if (annotationMirror.isEmpty()) {
502+
return ActivityValueMapperRecord.generatedFor(
503+
ClassName.get(exportTypeElement),
504+
missionModelElement);
505+
}
506+
507+
final var mapperType = (DeclaredType) getAnnotationAttribute(annotationMirror.get(), "value")
508+
.orElseThrow(() -> new InvalidMissionModelException(
509+
"Unable to get value attribute of annotation",
510+
exportTypeElement,
511+
annotationMirror.get()))
512+
.getValue();
513+
514+
return ActivityValueMapperRecord.custom(
492515
ClassName.get((TypeElement) mapperType.asElement()));
493516
}
494517

merlin-framework-processor/src/main/java/gov/nasa/jpl/aerie/merlin/processor/MissionModelProcessor.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package gov.nasa.jpl.aerie.merlin.processor;
22

3+
import com.squareup.javapoet.JavaFile;
4+
import com.squareup.javapoet.TypeName;
35
import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType;
46
import gov.nasa.jpl.aerie.merlin.framework.annotations.AutoValueMapper;
7+
import gov.nasa.jpl.aerie.merlin.framework.annotations.Export;
58
import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel;
69
import gov.nasa.jpl.aerie.merlin.processor.generator.MissionModelGenerator;
710
import gov.nasa.jpl.aerie.merlin.processor.metamodel.MissionModelRecord;
@@ -26,6 +29,7 @@
2629
import java.util.Collections;
2730
import java.util.HashSet;
2831
import java.util.List;
32+
import java.util.Optional;
2933
import java.util.Set;
3034
import java.util.stream.Collectors;
3135

@@ -92,9 +96,10 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
9296
for (final var element : roundEnv.getElementsAnnotatedWith(MissionModel.class)) {
9397
final var recordAutoValueMapperRequests = roundEnv.getElementsAnnotatedWith(AutoValueMapper.Record.class);
9498
final var annotationAutoValueMapperRequests = roundEnv.getElementsAnnotatedWith(AutoValueMapper.Annotation.class);
99+
95100
final var packageElement = (PackageElement) element;
96101
try {
97-
final var missionModelRecord$ = missionModelParser.parseMissionModel(packageElement);
102+
final var missionModelRecord$ = missionModelParser.parseMissionModel(packageElement); //todo: add typerules for activity parameters
98103

99104
final var concatenatedTypeRules = new ArrayList<>(missionModelRecord$.typeRules());
100105
for (final var request : recordAutoValueMapperRequests) {
@@ -103,6 +108,9 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
103108
for (final var request : annotationAutoValueMapperRequests) {
104109
concatenatedTypeRules.add(AutoValueMappers.annotationTypeRule(request, missionModelRecord$.getAutoValueMappersName()));
105110
}
111+
for(final var request : this.foundActivityTypes) {
112+
concatenatedTypeRules.add(AutoValueMappers.activityTypeRule(request, missionModelRecord$.getActivityValueMappers()));
113+
}
106114

107115
final var missionModelRecord = new MissionModelRecord(
108116
missionModelRecord$.$package(),
@@ -113,6 +121,7 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
113121
missionModelRecord$.activityTypes()
114122
);
115123

124+
116125
final var generatedFiles = new ArrayList<>(List.of(
117126
missionModelGen.generateMerlinPlugin(missionModelRecord),
118127
missionModelGen.generateSchedulerPlugin(missionModelRecord)));
@@ -134,13 +143,16 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
134143
annotationAutoValueMapperRequests);
135144
generatedFiles.add(autoValueMappers);
136145

146+
137147
for (final var activityRecord : missionModelRecord.activityTypes()) {
138148
this.ownedActivityTypes.add(activityRecord.inputType().declaration());
139-
if (!activityRecord.inputType().mapper().isCustom) {
149+
if (!activityRecord.inputType().activityMapper().isCustom) {
140150
missionModelGen.generateActivityMapper(missionModelRecord, activityRecord).ifPresent(generatedFiles::add);
141151
}
142152
}
143153

154+
generatedFiles.add(missionModelGen.generateActivityValueMappers(missionModelRecord));
155+
144156
for (final var generatedFile : generatedFiles) {
145157
this.messager.printMessage(
146158
Diagnostic.Kind.NOTE,

0 commit comments

Comments
 (0)