Skip to content

Commit

Permalink
Pass simulation start to mission model if expected
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrosemurgy committed Aug 15, 2022
1 parent 4103572 commit 5d210cb
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gov.nasa.jpl.aerie.merlin.processor;

import com.squareup.javapoet.ClassName;
import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType;
import gov.nasa.jpl.aerie.merlin.framework.annotations.Export;
import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel;
Expand Down Expand Up @@ -30,6 +31,7 @@
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand All @@ -49,22 +51,72 @@ public MissionModelRecord parseMissionModel(final PackageElement missionModelEle
throws InvalidMissionModelException
{
final var topLevelModel = this.getMissionModelModel(missionModelElement);
final var modelConfigurationType = this.getMissionModelConfigurationType(missionModelElement);
final var activityTypes = new ArrayList<ActivityTypeRecord>();
final var typeRules = new ArrayList<TypeRule>();

final var typeRules = new ArrayList<TypeRule>();
for (final var factory : this.getMissionModelMapperClasses(missionModelElement)) {
typeRules.addAll(this.parseValueMappers(factory));
}

final var activityTypes = new ArrayList<ActivityTypeRecord>();
for (final var activityTypeElement : this.getMissionModelActivityTypes(missionModelElement)) {
activityTypes.add(this.parseActivityType(missionModelElement, activityTypeElement));
}

return new MissionModelRecord(missionModelElement, topLevelModel, modelConfigurationType, typeRules, activityTypes);
return new MissionModelRecord(
missionModelElement,
topLevelModel.type,
topLevelModel.expectsSimulationStart,
topLevelModel.configurationType,
typeRules,
activityTypes);
}

private record MissionModelTypeRecord(
TypeElement type,
boolean expectsSimulationStart,
Optional<ConfigurationTypeRecord> configurationType) { }

private MissionModelTypeRecord getMissionModelModel(final PackageElement missionModelElement)
throws InvalidMissionModelException
{
final var configurationType = this.getMissionModelConfigurationType(missionModelElement);
final var modelTypeElement = this.getMissionModelTypeElement(missionModelElement);

final var parameters = this.getMissionModelConstructor(modelTypeElement).getParameters().stream()
.map(p -> ClassName.get(p.asType()))
.toList();
final var nParameters = parameters.size();
final var foundSimulationStart = parameters.contains(ClassName.get(Instant.class));

// Ensure model only has one constructor, one of:
// - (Registrar, Instant, Configuration)
// - (Registrar, Configuration)
// - (Registrar, Instant)
// - (Registrar)
final var expectedParameters = configurationType
// Configuration expected
.map(configType -> foundSimulationStart && nParameters > 2 ? // If simulation start `Instant` truly is expected
List.of(ClassName.get(Registrar.class), ClassName.get(Instant.class), ClassName.get(configType.declaration())) :
List.of(ClassName.get(Registrar.class), ClassName.get(configType.declaration())))
// No configuration expected
.orElseGet(() -> foundSimulationStart && nParameters > 1 ? // If simulation start `Instant` truly is expected
List.of(ClassName.get(Registrar.class), ClassName.get(Instant.class)) :
List.of(ClassName.get(Registrar.class)));

if (!parameters.equals(expectedParameters)) {
throw new InvalidMissionModelException(
"The top-level model's constructor is expected to accept %s, found: %s"
.formatted(expectedParameters.toString(), parameters.toString()),
missionModelElement);
}

// TODO: Consider enrolling the given model in a dependency injection framework,
// such that the Cursor can be injected like any other constructor argument,
// and indeed such that other arguments can flexibly be supported.
return new MissionModelTypeRecord(modelTypeElement, foundSimulationStart, configurationType);
}

private TypeElement getMissionModelModel(final PackageElement missionModelElement)
private TypeElement getMissionModelTypeElement(final PackageElement missionModelElement)
throws InvalidMissionModelException
{
final var annotationMirror = this
Expand All @@ -73,23 +125,34 @@ private TypeElement getMissionModelModel(final PackageElement missionModelElemen
"The missionModel package is somehow missing an @MissionModel annotation",
missionModelElement));

final var modelAttribute =
getAnnotationAttribute(annotationMirror, "model").orElseThrow();
if (!(modelAttribute.getValue() instanceof DeclaredType)) {
final var modelAttribute = getAnnotationAttribute(annotationMirror, "model").orElseThrow();
if (!(modelAttribute.getValue() instanceof final DeclaredType model)) {
throw new InvalidMissionModelException(
"The top-level model is not yet defined",
missionModelElement,
annotationMirror,
modelAttribute);
}
return (TypeElement) model.asElement();
}

// TODO: Check that the given model conforms to the expected protocol.
// * Has a (1,1) constructor that takes a Registrar.
// It doesn't actually need to subclass Model.
// TODO: Consider enrolling the given model in a dependency injection framework,
// such that the Cursor can be injected like any other constructor argument,
// and indeed such that other arguments can flexibly be supported.
return (TypeElement) ((DeclaredType) modelAttribute.getValue()).asElement();
private ExecutableElement getMissionModelConstructor(final TypeElement missionModelTypeElement)
throws InvalidMissionModelException
{
final var constructors = missionModelTypeElement.getEnclosedElements().stream()
.filter(e -> e.getKind() == ElementKind.CONSTRUCTOR && e.getModifiers().contains(Modifier.PUBLIC))
.toList();
if (constructors.isEmpty()) {
throw new InvalidMissionModelException(
"The top-level model declares no public constructor",
missionModelTypeElement);
}
if (constructors.size() > 1) {
throw new InvalidMissionModelException(
"The top-level model declares more than one public constructor",
missionModelTypeElement);
}
return (ExecutableElement) constructors.get(0);
}

private Optional<ConfigurationTypeRecord> getMissionModelConfigurationType(final PackageElement missionModelElement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -233,16 +232,7 @@ public JavaFile generateMissionModelFactory(final MissionModelRecord missionMode
gov.nasa.jpl.aerie.merlin.framework.InitializationContext.class,
"executor",
"builder",
(missionModel.modelConfigurationType.isPresent())
? CodeBlock.of(
"new $T($L, $L)",
ClassName.get(missionModel.topLevelModel),
"registrar",
"configuration")
: CodeBlock.of(
"new $T($L))",
ClassName.get(missionModel.topLevelModel),
"registrar"))
generateMissionModelInstantiation(missionModel))
.addStatement(
"return new $T<>($L, $L, $L)",
gov.nasa.jpl.aerie.merlin.framework.RootModel.class,
Expand All @@ -258,6 +248,33 @@ public JavaFile generateMissionModelFactory(final MissionModelRecord missionMode
.build();
}

private static CodeBlock generateMissionModelInstantiation(final MissionModelRecord missionModel) {
final var modelClassName = ClassName.get(missionModel.topLevelModel);
return missionModel.modelConfigurationType
.map(configType -> missionModel.expectsSimulationStart ?
CodeBlock.of(
"new $T($L, $L, $L)",
modelClassName,
"registrar",
"simulationStart",
"configuration") :
CodeBlock.of(
"new $T($L, $L)",
modelClassName,
"registrar",
"configuration"))
.orElseGet(() -> missionModel.expectsSimulationStart ?
CodeBlock.of(
"new $T($L, $L)",
modelClassName,
"registrar",
"simulationStart") :
CodeBlock.of(
"new $T($L)",
modelClassName,
"registrar"));
}

/** Generate `GeneratedSchedulerModel` class. */
public JavaFile generateSchedulerModel(final MissionModelRecord missionModel) {
final var typeName = missionModel.getSchedulerModelName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ public final class MissionModelRecord {
public final TypeElement topLevelModel;
public final List<TypeRule> typeRules;
public final List<ActivityTypeRecord> activityTypes;
public final boolean expectsSimulationStart;
public final Optional<ConfigurationTypeRecord> modelConfigurationType;

public MissionModelRecord(
final PackageElement $package,
final TypeElement topLevelModel,
final boolean expectsSimulationStart,
final Optional<ConfigurationTypeRecord> modelConfigurationType,
final List<TypeRule> typeRules,
final List<ActivityTypeRecord> activityTypes)
{
this.$package = Objects.requireNonNull($package);
this.topLevelModel = Objects.requireNonNull(topLevelModel);
this.expectsSimulationStart = expectsSimulationStart;
this.modelConfigurationType = Objects.requireNonNull(modelConfigurationType);
this.typeRules = Objects.requireNonNull(typeRules);
this.activityTypes = Objects.requireNonNull(activityTypes);
Expand Down

0 comments on commit 5d210cb

Please sign in to comment.