Skip to content

Commit 184b752

Browse files
Add default arguments to procedural goals and constraints
1 parent 17f31fc commit 184b752

File tree

20 files changed

+992
-44
lines changed

20 files changed

+992
-44
lines changed

e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/DumbRecurrenceGoal.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
22

3+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.WithDefaults;
34
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
45
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
56
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
@@ -17,7 +18,7 @@
1718
* one every 6hrs.
1819
*/
1920
@SchedulingProcedure
20-
public record DumbRecurrenceGoal(int quantity) implements Goal {
21+
public record DumbRecurrenceGoal(int quantity, int biteSize) implements Goal {
2122
@Override
2223
public void run(@NotNull final EditablePlan plan) {
2324
final var firstTime = Duration.hours(24);
@@ -27,7 +28,7 @@ public void run(@NotNull final EditablePlan plan) {
2728
for (var i = 0; i < quantity; i++) {
2829
plan.create(
2930
new NewDirective(
30-
new AnyDirective(Map.of("biteSize", SerializedValue.of(1))),
31+
new AnyDirective(Map.of("biteSize", SerializedValue.of(biteSize))),
3132
"It's a bite banana activity",
3233
"BiteBanana",
3334
new DirectiveStart.Absolute(currentTime)
@@ -37,4 +38,11 @@ public void run(@NotNull final EditablePlan plan) {
3738
}
3839
plan.commit();
3940
}
41+
42+
/**
43+
* Default parameters. Quantity is provided but biteSize is not so it is required.
44+
*/
45+
public static @WithDefaults class Defaults {
46+
int quantity = 360;
47+
}
4048
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.Template;
6+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
7+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.NewDirective;
8+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.AnyDirective;
9+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
10+
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
11+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
12+
import org.jetbrains.annotations.NotNull;
13+
14+
import java.util.Map;
15+
16+
/**
17+
* Waits 24hrs into the plan, then places `quantity` number of BiteBanana activities,
18+
* one every 6hrs.
19+
*/
20+
@SchedulingProcedure
21+
public record DumbRecurrenceGoalWithTemplateDefaults(int quantity, int biteSize) implements Goal {
22+
@Override
23+
public void run(@NotNull final EditablePlan plan) {
24+
final var firstTime = Duration.hours(24);
25+
final var step = Duration.hours(6);
26+
27+
var currentTime = firstTime;
28+
for (var i = 0; i < quantity; i++) {
29+
plan.create(
30+
new NewDirective(
31+
new AnyDirective(Map.of("biteSize", SerializedValue.of(biteSize))),
32+
"It's a bite banana activity",
33+
"BiteBanana",
34+
new DirectiveStart.Absolute(currentTime)
35+
)
36+
);
37+
currentTime = currentTime.plus(step);
38+
}
39+
plan.commit();
40+
}
41+
42+
public static @Template DumbRecurrenceGoalWithTemplateDefaults create() {
43+
return new DumbRecurrenceGoalWithTemplateDefaults(10, 10);
44+
}
45+
}
46+

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ void proceduralUploadWorks() throws IOException {
5555
void executeSchedulingRunWithoutArguments() throws IOException {
5656
final var resp = hasura.awaitFailingScheduling(specId);
5757
final var message = resp.reason().getString("message");
58-
assertTrue(message.contains("java.lang.RuntimeException: Record missing key Component[name=quantity"));
58+
assertTrue(message.contains("java.lang.RuntimeException: Record missing key Component[name=biteSize"));
5959
}
6060

6161
/**
6262
* Run a spec with one procedure in it
6363
*/
6464
@Test
6565
void executeSchedulingRunWithArguments() throws IOException {
66-
final var args = Json.createObjectBuilder().add("quantity", 2).build();
66+
final var args = Json.createObjectBuilder().add("biteSize", 2).build();
6767

6868
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
6969

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling;
2+
3+
import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId;
4+
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
5+
import org.junit.jupiter.api.AfterEach;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
9+
import javax.json.Json;
10+
import java.io.IOException;
11+
import java.util.Objects;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
public class TemplateDefaultsTests extends ProceduralSchedulingSetup {
17+
private int procedureJarId;
18+
private GoalInvocationId procedureId;
19+
20+
@BeforeEach
21+
void localBeforeEach() throws IOException {
22+
try (final var gateway = new GatewayRequests(playwright)) {
23+
procedureJarId = gateway.uploadJarFile("build/libs/DumbRecurrenceGoalWithTemplateDefaults.jar");
24+
// Add Scheduling Procedure
25+
procedureId = hasura.createSchedulingSpecProcedure(
26+
"Test Scheduling Procedure",
27+
procedureJarId,
28+
specId,
29+
0
30+
);
31+
}
32+
}
33+
34+
@AfterEach
35+
void localAfterEach() throws IOException {
36+
hasura.deleteSchedulingGoal(procedureId.goalId());
37+
}
38+
39+
/**
40+
* Upload a procedure jar and add to spec
41+
*/
42+
@Test
43+
void proceduralUploadWorks() throws IOException {
44+
final var ids = hasura.getSchedulingSpecGoalIds(specId);
45+
46+
assertEquals(1, ids.size());
47+
assertEquals(procedureId.goalId(), ids.getFirst());
48+
}
49+
50+
/**
51+
* Running without argument works because of template defaults
52+
*/
53+
@Test
54+
void executeSchedulingRunWithoutArguments() throws IOException {
55+
hasura.awaitScheduling(specId);
56+
57+
final var plan = hasura.getPlan(planId);
58+
final var activities = plan.activityDirectives();
59+
60+
assertEquals(2, activities.size());
61+
62+
assertTrue(activities.stream().anyMatch(
63+
it ->
64+
Objects.equals(it.type(), "BiteBanana") &&
65+
Objects.equals(it.startOffset(), "24:00:00") &&
66+
Objects.equals(it.arguments().getString("biteSize"), "10") &&
67+
Objects.equals(it.arguments().getString("quantity"), "10")
68+
));
69+
70+
assertTrue(activities.stream().anyMatch(
71+
it ->
72+
Objects.equals(it.type(), "BiteBanana") &&
73+
Objects.equals(it.startOffset(), "30:00:00") &&
74+
Objects.equals(it.arguments().getString("biteSize"), "10") &&
75+
Objects.equals(it.arguments().getString("quantity"), "10")
76+
));
77+
}
78+
79+
/**
80+
* Run a spec with one procedure in it and just one of the argument. The other should be provided by the template.
81+
*/
82+
@Test
83+
void executeSchedulingRunWithOneArgument() throws IOException {
84+
final var args = Json.createObjectBuilder().add("biteSize", 2).build();
85+
86+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
87+
88+
hasura.awaitScheduling(specId);
89+
90+
final var plan = hasura.getPlan(planId);
91+
final var activities = plan.activityDirectives();
92+
93+
assertEquals(2, activities.size());
94+
95+
assertTrue(activities.stream().anyMatch(
96+
it ->
97+
Objects.equals(it.type(), "BiteBanana") &&
98+
Objects.equals(it.startOffset(), "24:00:00") &&
99+
Objects.equals(it.arguments().getString("biteSize"), "2") &&
100+
Objects.equals(it.arguments().getString("quantity"), "10")
101+
));
102+
103+
assertTrue(activities.stream().anyMatch(
104+
it ->
105+
Objects.equals(it.type(), "BiteBanana") &&
106+
Objects.equals(it.startOffset(), "30:00:00") &&
107+
Objects.equals(it.arguments().getString("biteSize"), "2") &&
108+
Objects.equals(it.arguments().getString("quantity"), "10")
109+
));
110+
}
111+
112+
}

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/models/ExecutableConstraint.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import gov.nasa.jpl.aerie.constraints.model.SimulationResults;
66
import gov.nasa.jpl.aerie.constraints.model.Violation;
77
import gov.nasa.jpl.aerie.constraints.tree.Expression;
8-
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
8+
import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException;
99
import org.jetbrains.annotations.NotNull;
1010

1111
import gov.nasa.ammos.aerie.procedural.constraints.ProcedureMapper;
@@ -84,9 +84,14 @@ public ProceduralConstraintResult run(
8484
throw new RuntimeException(e);
8585
}
8686

87-
final var violations = Violation.fromProceduralViolations(procedureMapper
88-
.deserialize(SerializedValue.of(record.arguments()))
89-
.run(plan, simResults), merlinResults);
87+
final List<Violation> violations;
88+
try {
89+
violations = Violation.fromProceduralViolations(procedureMapper
90+
.getInputType().instantiate(record.arguments())
91+
.run(plan, simResults), merlinResults);
92+
} catch (InstantiationException e) {
93+
throw new RuntimeException(e);
94+
}
9095

9196
return new ProceduralConstraintResult(violations, record);
9297
}
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package gov.nasa.ammos.aerie.procedural.constraints
22

3-
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue
3+
import gov.nasa.jpl.aerie.merlin.protocol.model.InputType
44
import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema
55

66
interface ProcedureMapper<T: Constraint> {
77
fun valueSchema(): ValueSchema
8-
fun serialize(procedure: T): SerializedValue
9-
fun deserialize(arguments: SerializedValue): T
8+
fun getInputType(): InputType<T>
109
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package gov.nasa.ammos.aerie.procedural.processor;
2+
3+
import com.squareup.javapoet.ClassName;
4+
import com.squareup.javapoet.CodeBlock;
5+
import com.squareup.javapoet.MethodSpec;
6+
import com.squareup.javapoet.ParameterizedTypeName;
7+
import com.squareup.javapoet.TypeName;
8+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.Template;
9+
import gov.nasa.jpl.aerie.merlin.protocol.types.UnconstructableArgumentException;
10+
import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException;
11+
12+
import javax.lang.model.element.ElementKind;
13+
import javax.lang.model.element.Modifier;
14+
import java.util.List;
15+
import java.util.Optional;
16+
import java.util.stream.Collectors;
17+
18+
/** Method maker for defaults style where all default arguments are provided within a @Template static method. */
19+
/*package-private*/ final class AllStaticallyDefinedMethodMaker extends MapperMethodMaker {
20+
21+
public AllStaticallyDefinedMethodMaker(final InputTypeRecord inputType) {
22+
super(inputType);
23+
}
24+
25+
@Override
26+
public MethodSpec makeInstantiateMethod() {
27+
final var activityTypeName = inputType.declaration().getSimpleName().toString();
28+
29+
var methodBuilder = MethodSpec.methodBuilder("instantiate")
30+
.addModifiers(Modifier.PUBLIC)
31+
.addAnnotation(Override.class)
32+
.returns(TypeName.get(inputType.declaration().asType()))
33+
.addException(InstantiationException.class)
34+
.addParameter(
35+
ParameterizedTypeName.get(
36+
java.util.Map.class,
37+
String.class,
38+
gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue.class),
39+
"arguments",
40+
Modifier.FINAL);
41+
42+
for (final var element : inputType.declaration().getEnclosedElements()) {
43+
if (element.getKind() != ElementKind.METHOD && element.getKind() != ElementKind.CONSTRUCTOR) continue;
44+
if (element.getAnnotation(Template.class) == null) continue;
45+
var templateName = element.getSimpleName().toString();
46+
methodBuilder = methodBuilder.addStatement("final var template = $L.$L()", activityTypeName, templateName);
47+
48+
methodBuilder = methodBuilder.addCode(
49+
inputType.parameters()
50+
.stream()
51+
.map(parameter -> CodeBlock
52+
.builder()
53+
.addStatement(
54+
"$T $L = $T$L",
55+
new TypePattern.ClassPattern(
56+
ClassName.get(Optional.class),
57+
List.of(TypePattern.from(parameter.type))).render(),
58+
parameter.name,
59+
Optional.class,
60+
".ofNullable(template." + parameter.name + "())"
61+
)
62+
)
63+
.reduce(CodeBlock.builder(), (x, y) -> x.add(y.build()))
64+
.build()).addCode("\n");
65+
66+
methodBuilder = makeArgumentAssignments(methodBuilder, (builder, parameter) -> builder
67+
.addStatement(
68+
"$L = $L(this.mapper_$L.deserializeValue($L.getValue())$W.getSuccessOrThrow(failure -> new $T(\"$L\", failure)))",
69+
parameter.name,
70+
"Optional.ofNullable",
71+
parameter.name,
72+
"entry",
73+
UnconstructableArgumentException.class,
74+
parameter.name));
75+
break;
76+
}
77+
78+
// Add return statement with instantiation of class with parameters
79+
methodBuilder = methodBuilder.addStatement(
80+
"return new $T($L)",
81+
inputType.declaration(),
82+
inputType.parameters().stream().map(
83+
parameter -> parameter.name + ".get()").collect(Collectors.joining(", ")));
84+
85+
return methodBuilder.build();
86+
}
87+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package gov.nasa.ammos.aerie.procedural.processor;
2+
3+
/**
4+
* Export defaults "style" refers to how an exporter's
5+
* default arguments have been defined within the mission model.
6+
*/
7+
public enum ExportDefaultsStyle {
8+
AllStaticallyDefined, // All default arguments provided within @Template static method
9+
SomeStaticallyDefined, // Some arguments provided within @WithDefaults static class
10+
NoneDefined // No default arguments provided
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package gov.nasa.ammos.aerie.procedural.processor;
2+
3+
import javax.lang.model.element.TypeElement;
4+
import java.util.List;
5+
6+
public record InputTypeRecord(
7+
String name,
8+
TypeElement declaration,
9+
List<ParameterRecord> parameters,
10+
MapperRecord mapper,
11+
ExportDefaultsStyle defaultsStyle
12+
) {}

0 commit comments

Comments
 (0)