Skip to main content

Program Models

Each model has a root element. This element is referred to as the Program. A Program has a ElementClass and a ProgramType.

  • ElementClass: ElementClass, with role program.
  • ProgramType: description of what kind of program we wish to expand from the model.

To make this more concrete, we can look at the elements metamodel and the JEE Application stack.

  • Application is a program ElementClass, which is described in the elements ontology.
  • There is a JeeApplication ProgramType. This describes how to load the Application model, where to expand and which layers are used. It also provides the logic for importing Components.
ProgramTypes and technologies

One program ElementClass can have multiple ProgramTypes. This corresponds with the multiple ways how that model can be expanded into code. E.g. an Application model can be used to generate a JEE application. But, the same model can also be used to generate a Spring application or any other technology.

Program Metamodel

To model a new class of Programs, define an ElementClass with role program.

model/elementClasses/Executable.xml
<elementClass name="Executable">
<isGloballyUnique>false</isGloballyUnique>
<isAbstract>false</isAbstract>
<isSealed>false</isSealed>
<group name="custom" ontology="demo"/>
<role name="program"/>
<attributes>
<attribute name="name">
<isIdentifier>true</isIdentifier>
<dataType name="Name" ontology="demo"/>
</attribute>
</attributes>
</elementClass>

Once you've expanded this, you'll need to provide some implementations:

ProgramFactory

The ProgramFactory is a class that provides implementations to import the models.

   ProgramFactory example

public class ExecutableFactory
implements ProgramFactory<ExecutableDataRef, ExecutableTree> {

/**
* ProgramExpansionConverter translates the reference in the expansionSettings to a DataRef
* which can be used by the ProgramReader class
*/
@Override
public ProgramExpansionToDataRefConverter<ExecutableDataRef> getProgramExpansionConverter(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return programExpansion -> ExecutableDataRef.withFunctionalKey(programExpansion.getTarget());
}

/**
* The ModuleRetriever is used to decide which modules to load.
* See documentation of Modules for more information.
*/
@Override
public ProgramModuleRetriever<ExecutableTree, ModuleTypeComposite> getModuleRetriever(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return Collections.emptyList();
}

/*
Several Classes are expanded for Program ElementClasses.
In most cases, it is enough to use these classes directly.
*/
@Override
public ProgramReader<ExecutableDataRef, ExecutableTree> getProgramReader(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramReader(modelLoadingContext, programTypeComposite);
}

@Override
public ProgramLoader<ExecutableTree> getProgramLoader(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramLoader(modelLoadingContext, programTypeComposite);
}

@Override
public ProgramEnricher getProgramEnricher(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramEnricher(modelLoadingContext, programTypeComposite);
}

@Override
public ProgramWriter<ExecutableTree> getProgramWriter(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programType) {
return new ExecutableProgramWriter(modelLoadingContext, programType);
}

}

ProgramTypes.xml

The metamodel project contains a programTypes.xml file with a custom anchor to add ProgramTypes.

  • name and component are combined to make a unique key.
  • rootExpandableElement refers to the ElementClass in the metamodel.
  • programFactory is the fully qualified name of the ProgramFactory implementation.
  • modelDirectory is a template for the location of the model files.
  • Similarly, sourceDirectory describes the location for harvest and ext files.
  • expansionDirectory is used for the target directory for Expanders.
  • layerTypes lists the layerTypes of the Program.

   ProgramType example

<dataResource type="expansionControl::ProgramType">
<!-- anchor:custom-program-types:start -->
<programType name="Java Cli">
<component>demo</component>
<rootExpandableElement component="demo" name="Executable"/>
<programFactory>org.normalizedsystems.demo.ExecutableFactory</programFactory>
<modelDirectory>$source.rootDirectory$/executables/$executable.name$/model</modelDirectory>
<sourceDirectory>$source.rootDirectory$/executables/$executable.name$</sourceDirectory>
<expansionDirectory>$expansion.rootDirectory$/$executable.name$</expansionDirectory>
<layerTypes>
<layerType name="EXEC_ROOT"/>
<layerType name="EXEC_CLI"/>
</layerTypes>
</programType>
<!-- anchor:custom-program-types:end -->
</dataResource>
Program Layers

Expanders always target a specific LayerType. Note that Expanders will only run if that LayerType is included in the ProgramType. (Except for Elements in a Module, which use the ModuleType Layers.)

This gives the Expander developer control over which Expanders apply to which ProgramTypes.

Map Technologies

When expanding, Expanders are filtered based on the Technologies that are present in the Program. To add these technologies, a customization is needed in the ProgramEnricher class.

   Example how to add Technologies

  // anchor:custom-after-transform:start
List<TechnologyComposite> technologies = new ArrayList<>(program.getTechnologies());

// Fetch and add PICOCLI Technology
modelLoadingContext.getDataRegistry()
.getComposite("PICOCLI", TechnologyComposite.class)
.ifPresent(technologies::add);

// Maybe add some technologies referenced in the Model
technologies.add(composite.getDataBaseTechnology());
technologies.add(composite.getViewTechnology());

program.setTechnologies(technologies);
// anchor:custom-after-transform:end
COMMON

The COMMON technology is always present on the Program. Hence Expanders targeting COMMON will always be applicable. (Provided all other conditions are met.)

Usage in tests

To be able to define test models using a program ElementClass, you will need to add an implementation of the modelSpec(Spec) method.

   modelSpec() example

ExecutableSpecFactory.java
  // anchor:custom-methods:start
@Override
public ModelSpec<ExecutableComposite, ExecutableComposite> modelSpec() {
return model(spec);
}
// anchor:custom-methods:end