Auto-Implementation

Moxie's auto-implementation system enables you to provide custom Java code to implement behaviors and interactions with external tools only where you need to. This frees you from the burden of having to provide code for every single block, property, and operation in your simulation.

If you do not provide a Java implementation for a block, Moxie will auto-generate an implementation for that block, including its properties and operations, in memory at runtime. It uses public get methods for properties, and returns the appropriate Property type. Operations with return types will return the default value for those return types (null for most types). Operations with no return type will be generated with an empty body.

If you do provide a Java implementation for a block, you can choose which properties and operations you want to implement without having to provide code for the others. You can do this by using abstract classes as discussed below.

Interfaces

Annotating an interface with @AutoDelegateImplementation will instruct the Moxie Engine to generate a concrete implementation for it in memory at runtime. This is useful at development time when you want to reference a block in your IDE for which you do not have a corresponding Java delegate. Moxie provides a utility to generate the Java code for these interfaces for you. The delegate code generator saves you development time and reduces human error by ensuring all of the block's properties and operations are correctly spelled, typed, and annotated. It also provides a mechanism for keeping your delegate property and method signatures in sync with your model when it changes. Simply generate code for the interface again and overwrite the previous version in your development project.

Figure 1 shows the Passenger interface from the hot air balloon ride example. For this example, assume there are no concrete implementations of Passenger. Since Passenger is annotated with @AutoDelegateImplementation, the Moxie Engine will auto-generate a concrete implementation for it in memory at runtime.

package com.agi.moxie.samples.balloonride.structure;

import com.agi.moxie.api.annotations.DelegateFor;
import com.agi.moxie.api.models.Property;
import com.agi.moxie.delegates.base.FixedDuration;
import com.agi.moxie.delegates.base.Iso8601TimeInstant;
import java.lang.Boolean;

@AutoDelegateImplementation
@DelegateFor("Structure::Passenger")
public interface Passenger extends SpatialEntity {

    Property<Iso8601TimeInstant> appointmentTimeProperty();

    Property<FixedDuration> photoDurationProperty();

    Property<Boolean> isSatisfiedWithPhotoProperty();

    Property<Boolean> takingPhotoProperty();

    void embark(SpatialEntity vehicle);

    void disembark();

    void takePhoto();
}

Figure 1: The Passenger interface with auto-implementation

Partially implemented abstract classes

You may encounter situations where you want to provide a custom Java implementation for a specific block property or operation, but not all of them. This is referred to in Moxie as partial implementation, and you do this using abstract classes. When you annotate an abstract class with @AutoDelegateImplementation, you can provide custom implementations for the properties and operations you choose and just specify the signatures for the rest. This is because Moxie generates in-memory default implementations at runtime for every property and operation you did not implement yourself. Similarly to interfaces, Moxie's delegate code generator will generate these abstract classes for you. This way, you can just fill in the custom code where you need it, and nothing more.

When you annotate an abstract class with @AutoDelegateImplementation, Moxie auto-generates concrete implementations in memory at runtime for abstract methods, including abstract Property methods. Moxie generates backing fields for properties, which support change tracking, using BasicProperty by default. Support for call events is also included with the auto-generated implementations for block operations.

Concrete implementations provided by you remain as is, with one exception. Moxie overrides your custom implementation for operations with logic to support call events, followed by a callback into your custom implementation.

Since Moxie overrides your custom implementations with callbacks into them, you cannot use final methods. Otherwise, an exception will be thrown at runtime.

All auto-implemented properties are guaranteed to be configured and available prior to the evaluation of any other properties or operations in the simulation. However, when implementing an injection constructor in your abstract class, it will get called before all of the auto-implemented properties are initialized. So, while you can accept @InjectBySlot values to obtain property input values from the SysML instance specifications, the auto-implemented properties will not be fully configured until all delegate instances are initialized. If you need to access an instance of a com.agi.moxie.api.models.Property from inside a constructor, you must create and initialize your own manually implemented backing field.

Figure 2 shows the partially implemented BalloonDelegate abstract class from the hot air balloon ride example.

package com.agi.moxie.samples.balloonride.structure.impl;

import com.agi.moxie.api.MoxieTime;
import com.agi.moxie.api.TimeProvider;
import com.agi.moxie.api.annotations.AutoDelegateImplementation;
import com.agi.moxie.api.annotations.DefaultDelegate;
import com.agi.moxie.api.annotations.InjectByName;
import com.agi.moxie.api.models.BasicProperty;
import com.agi.moxie.api.models.Property;
import com.agi.moxie.delegates.core.DynamicScalarValue;
import com.agi.moxie.delegates.core.ScalarValue;
import com.agi.moxie.samples.balloonride.structure.Balloon;
import com.agi.moxie.samples.balloonride.structure.Passenger;

import java.util.Collection;
import java.util.function.Function;

@AutoDelegateImplementation
@DefaultDelegate
public abstract class BalloonDelegate implements Balloon {

    private final TimeProvider mTimeProvider;
    private final BasicProperty<ScalarValue> mAltitude;
    private final Function<MoxieTime, Double> mStationaryFunction = (MoxieTime time) -> 0D;

    // Injection constructor
    public BalloonDelegate(@InjectByName("timeProvider") TimeProvider timeProvider) {
        mTimeProvider = timeProvider;
        mAltitude = new BasicProperty<>(timeProvider,
        new DynamicScalarValue(mTimeProvider, mStationaryFunction, mStationaryFunction));
    }

    @Override
    public Property<ScalarValue> altitudeProperty() {
        return mAltitude;
    }

    public void turnBurnerOn() {
        ScalarValue newAltitudeModel = adjustAltitudeModel(climbAccelerationProperty().getValue());
        mAltitude.setValue(newAltitudeModel);
    }

    public void turnBurnerOff() {
        ScalarValue newAltitudeModel = adjustAltitudeModel(-climbAccelerationProperty().getValue());
        mAltitude.setValue(newAltitudeModel);
    }

    @Override
    public void tieDown() {
        mAltitude.setValue(new DynamicScalarValue(mTimeProvider, mStationaryFunction, mStationaryFunction));
    }

    // This will have an implementation provided automatically if not overridden in a derived class.
    public abstract Collection<Passenger> passengersProperty();

    /**
     * Provides a new {@link DynamicScalarValue} with the given acceleration.
     *
     * @param climbAcceleration the acceleration to update the physics to use.
     * @return the new {@link DynamicScalarValue} that reflects the given acceleration
     */
    private DynamicScalarValue adjustAltitudeModel(Double climbAcceleration) {
        DynamicScalarValue dist = (DynamicScalarValue) mAltitude.getValue();
        MoxieTime currentTime = mTimeProvider.getCurrentTime();
        double mInitialAltitude = dist.getValueAt(currentTime);
        double mInitialAltitudeRate = dist.getRateAt(currentTime);
        DynamicScalarValue newDistance = new DynamicScalarValue(mTimeProvider, (MoxieTime time) -> {
            // Note that we are closing over 'currentTime' here, capturing the change in dynamics starting at the current event time.
            double t = currentTime.getDurationTo(time).getTotalSeconds();
            // The following is an extremely simplified model of the physics for a simple demonstration.
            // Ideally, this code would call into a more complex model over time as the system engineering lifecycle progresses.
            // Eventually, this could call into an external tool that would be executing the digital twin to analyze the full
            // heating and buoyancy profile for the balloon sub-system in accordance to the characteristics of the burner sub-system.
            // For now, we simply model a fixed acceleration modeling the buoyancy, air resistance, and other forces.
            return mInitialAltitude + mInitialAltitudeRate * t + 0.5 * climbAcceleration * t * t;
        }, (MoxieTime time) -> {
            double t = currentTime.getDurationTo(time).getTotalSeconds();
            return mInitialAltitudeRate + climbAcceleration * t;
        });
        return newDistance;
    }

}

Figure 2: The BalloonDelegate partially implemented abstract class

Partially implemented concrete classes

As with interfaces and abstract classes, when you add the @AutoDelegateImplementation annotation to a concrete class, Moxie generates the missing block properties and operations that do not have corresponding Java properties or methods at runtime. Moxie supports call events by default in the same way as abstract classes, so you cannot use final methods corresponding to block operations.

Differing signatures

Since Moxie implements missing properties and methods for you, any mistakes you make in the signatures of the properties and methods you do write will cause them to be ignored by Moxie at runtime. If you encounter cases where your code is not being called, be sure to check the spelling, casing, and type information to ensure it matches the model definition. You can also prevent these mistakes by using the delegate code generator to generate code for the delegates and their property and operation signatures for you.

When using partial implementation, @InjectBySlot and @InjectByName are both allowed. However, the wire-up process will operate on concretely, partially, and automatically implemented properties. Furthermore, the order in which the Moxie Engine does the wire-up is not guaranteed. However, all properties with slot values are guaranteed to be set prior to executing any state machines.