Using Java Static Meta Classes

Motivation

To fully leverage the power of Aspect Models during application development generating the Java classes for Aspect Models is usually the first step. However, while it allows to easily and quickly build an application that uses the data of single Aspects, it does not help to build applications that can handle the data of Aspects in a generic way, that means: using the Meta Model.

What would be an example for such a case? Let’s assume a very simple application, that still shows very well, why using the Meta Model is mandatory despite the problem at hand seeming not very complex at first.

Let’s assume we’re starting our application with the well-known Movement Aspect, but possibly more Aspects are to come in the future.
To easily get a quick overview of the Aspect data we want to write a console application that pulls the data from the Aspect and prints it to console nicely formatted. Using a built-in mechanism like generated toString methods might work for a very first version and for simple Aspect Models, but our Aspect already contains nested entity structures and future ones could contain even more complex ones, with lists of entities within lists. And apart from that, we want to print them really nicely, with indentation and other formatting applied, maybe like this:

[Movement]
- Is moving: YES
- Speed: 35 km/h
- Speed limit warning: yellow
- Position:
  - Latitude: 45.2
  - Longitude: 32.7
  - Altitude: 17.0
...

Now, one possibility would of course be to write specific code that does exactly that for this Aspect.
Code like that however has multiple drawbacks: it’s tedious to write (maybe not for one or two Aspects, but most applications will work with way more Aspects), error-prone and - most importantly - it requires touching every time your Aspect Model changes.

A second option that often comes to mind when working with models of any kind is code generation, just as we use it for the Java POJOs through the respective tooling. While code generation is a great tool for situations where the input might change quite often, but the targeted use case does not, it isn’t the best fit for use cases, that most likely will evolve over time. Building applications with Aspect Models is most of the time exactly that - how you use the Aspect Data is often subject to different changes. Adapting a generator or template for that all the time in the end does not buy you much.

What we would need is a possibility to work on the Meta Model and Aspect Model at the same time, to be able to explicitly work with or iterate over its properties. Taking the Aspect Model from above, we could e.g. write code like this:

Movement movement = retrieveMovement();

for ( Property p : getAllProperties() ) {
   System.out.println( String.format( "- %s: %s", p.getName(), p.getValue( movement ) ) );
}

This code may not look very special at first glance or you might ask the question where’s the big difference compared to the other attempts we discussed above? Let’s take a deeper look.

Most obviously we are able to handle all properties of an Aspect with exactly one line - we print the property name and the value. This is the first major advantage, because as long as we do not have the need to apply very specific formatting to single elements, this single line will be a fit for any property of the Aspect - also those, that we might add to (or also remove from) the Aspect Model in the future.

This directly brings us to a related advantage, that is hidden in the loop itself: the getAllProperties() method. Where does this come from? We’ll see later on, that we use Static Meta Classes for that, but for now let’s assume, that this part will be generated. Remember that we said, code generation is great for anything where the use case does only rarely change? Getting a list of our Model’s properties is exactly such a use case. We want a list and that’s that.

This means, whenever we change our Aspect Model, we run the code generation step once to get the updated artifacts, and after that the code above will work still exactly the same - but it also will automatically pick up the changes to the list of our properties.

Even this over-simplified example already shows the great benefits of this approach, as it allows developers to write very stable generic code that can easily also perform much more complex tasks, as long as the input that is required is made up of an Aspect Model and its data.

Granted, code like above could also be written using a dynamically loaded Aspect Model (note the missing Static). So in order to close this motivation let’s assume another small requirement for our application that is at least rather ugly to implement: our Aspects not only carry the data we need for our daily work, but also a lot of data which is not strictly required to be printed - or maybe we just want to print it when some kind of verbose mode is enabled.
Something like this requires filtering the list of properties we use. Now how to do this? Of course, we apply some filter predicate and skip let’s say the properties called speed and speedLimitWarning. As we have to use string matching on human knowledge about the Aspect Model, we again can quickly identify two issues:

  • string matching code is always ugly and subject to typing errors

  • if we ever remove e.g. the property speedLimitWarning this will not break our code, but leave us with something that can be surprising to another developer taking over - code, that’s outdated and nobody noticed.

How would this requirement look like, if we now assume we already have such an accompanying static meta class being called MetaMovement for our Aspect?

Movement movement = retrieveMovement();
List<Property> allProperties = getAllProperties();
if (mode != "VERBOSE") {
   allProperties.removeAll( List.of( MetaMovement.SPEED_LIMIT, MetaMovement.SPEED_LIMIT_WARNING ) );
}

for ( Property p : getAllProperties() ) {
   System.out.println( String.format( "- %s: %s", p.getName(), p.getValue( movement ) ) );
}

This solves both problems described above and adds convenience on top:

  • the static class is generated from the Aspect Model - no need to repeat the property names by hand

  • the static class contains actual Meta Model elements - even no need for strings at all!

  • the intent of the code is easier to grasp

  • if we ever remove the property speedLimitWarning, the above code will not compile until we remove the now invalid property reference - we gained compile-time safety!

Basic Usage

To get started with using Static Meta Models within your application you need to generate the respective Java artifacts using either the Java Tooling or Maven plugin.

After the artifacts have been generated you’ll find for each Aspect and Entity a new source file called MetaElementName.java which contains the Static Meta Model.

All the following chapters again assume the Movement Aspect with its accompanying static MetaMovement class.

Meta Class Overview

General Information within the Meta Classes

The Meta Classes contain some general Model information within static fields:

Field Description Type (Sample) value

NAMESPACE

The namespace of this Model Element

String

urn:samm:org.eclipse.esmf.examples.movement:1.0.0#

MODEL_ELEMENT_URN

The full URN of this Model Element

String

urn:samm:org.eclipse.esmf.examples.movement:1.0.0#Movement

CHARACTERISTIC_NAMESPACE

The Characteristic Namespace of the Model

String

urn:samm:org.eclipse.esmf.samm:characteristic:2.2.0#

INSTANCE

The singleton instance of this Meta class. Used to access non-static information.

MetaMovement

N/A

Type and Property Information within the Meta Classes

The instances of the Meta Classes themselves can be used for application development as well as the contained information about Properties, depending on which Model information is required. Most applications will predominantly use the property information, however when implementations take multiple Aspects and/or Entities into account, also class-level information can be of great use.

Field or Method Description (Return) Type (Sample) value

getModelClass()

The Java Class for this Model Element

Class<MetaMovement>

MetaMovement.class

getAspectModelUrn()

The URN of this Model Element

AspectModelUrn

AspectModelUrn.fromUrn(MODEL_ELEMENT_URN)

getMetaModelVersion()

The used Meta Model version

KnownVersion

KnownVersion.SAMM_2_1_0

getName()

The name of this Model Element

String

Movement

getProperties()

All StaticProperty​s of this Model Element

List<StaticProperty<Movement, ?> or List<StaticProperty<? super Movement, ?>

Arrays.asList(SOME_STRING, SOME_INT, SOME_FLOAT);

getAllProperties()

All StaticProperty​s of this Model Element, including inherited properties. If no inheritance is used it returns the same value as getProperties()

List<StaticProperty<Movement, ?> or List<StaticProperty<? super Movement, ?>

Arrays.asList(SOME_STRING, SOME_INT, SOME_FLOAT);

For each property of the respective Model Element the Meta Class contains one StaticProperty field. The name of the field is derived from the property name, converting it into from camelCase to UPPER_UNDERSCORE. A property named firstName would result in the field FIRST_NAME.
Each property then again provides information through its member methods:

Method Description Return Type (Sample) value

getPropertyType()

The Java Class of this property including generics

Class<…​>

String.class, List.class

getContainingType()

The Java Class of the element containing this property (e.g. an Aspect or an Entity)

Class<…​>

Class<Movement>

getContainedType()

Only present on properties with container types like List, Set, Optional etc. The Java Class of the element contained within the container.

Class<…​>

Class<Movement>

isComplexType()

Whether this property has a complex type, e.g. an Entity.

boolean

true for complex types, false else

getValue(Movement object)

The property value of the given element instance

The property type

someStringValue (using the getter, e.g. object.getTestString())

Mutating element instances is also possible if the code generator was run with setter creation enabled. Be aware of the fact, that the mutator methods are always present, but will throw an UnsupportedOperationException if no setters have been generated!

Method Description Return Type Sample call

setValue(Movement object, PropertyType value)

Updates the property value of the given element instance

void

SPEED.setValue(movementInstance, 12.0f)

Types of Static Properties

Different kinds of Static Properties exist to reflect all possible elements within an Aspect Model:

Property Type Description Generics

StaticProperty<C, T>

A simple scalar property

C is the containing type (an Aspect or Entity)
T is the property type (e.g. String)

StaticContainerProperty<E, C, T>

A property with a container type like Optional or Collection

E is the containing type (an Aspect or Entity)
C is the type inside the container (e.g. String)
T is the property type (e.g. Optional<String>)

StaticUnitProperty<C, T>

A simple scalar property but with an additional method to get its Unit

C is the containing type (an Aspect or Entity)
T is the property type (e.g. String)

All of the above Property types have a counterpart with a Constraint, namely StaticConstraintProperty, StaticConstraintContainerProperty and StaticConstraintUnitProperty.
Their API and generics are the same, but they have an additional method to get a list of their constraints.

Writing Code using Static Meta Classes

Addressing Properties

Independent from what you’ll finally use a Property for it is important to understand how you can use them to navigate through your Model. Every Model Element that is a Property Container offers access to its properties - most prominently Aspects and Entities.
Their properties are directly addressed using the respective fields from their Meta Classes.

However, you can also go beyond that. Properties can be chained so that it’s possible to address properties nested inside your Model.

This is useful in situations where it is required to use nested properties as if they would belong to a higher Element. Such operations can be for example:

  • flattening data structures

  • filtering on criteria defined on nested properties

Property Chains are defined using type safe builders:

PropertyChain<Movement, BigDecimal> latitude =
    PropertyChain.from( MetaMovement.POSITION )
                 .to( MetaSpatialPosition.LATITUDE );

Property Chains can follow deeply nested structures and are not limited in that. Assuming another Aspect that contains the structure Aspectentity: EntitysubEntity: SubEntity we can define a chain like this:

PropertyChain<Aspect, String> nestedString =
    PropertyChain.from( MetaAspect.ENTITY )
                 .via( MetaEntity.SUB_ENTITY )
                 .to( MetaSubEntity.STRING_PROPERTY );

Container properties can also appear anywhere, either at the start or in the middle or end of a chain. The respective container type then will be propagated from the moment on it appears. One thing to note is, that for collection valued properties the concrete collection type is not preserved but always replaced with a List.

ContainerPropertyChain<Movement, Optional<Float>, Float> altitude =
    PropertyChain.from( MetaMovement.POSITION )
                 .to( MetaSpatialPosition.ALTITUDE );


ContainerPropertyChain<Aspect, List<String>, String> nestedEntityCollectionStrings =
    PropertyChain.from( MetaAspect.ENTITY )
                 .viaCollection( MetaEntity.SUB_ENTITY_LIST )
                 .to( MetaSubEntity.STRING_PROPERTY );

Accessing Property data

Static Properties can act as accessors and thus be used to retrieve the data they represent from instances of their enclosing Model Elements.

All Static Properties provide the method R getValue(C object) and additionally extend the interface Function<C, R> so that it is also possible to directly use them within stream operations like .map().

For example, simply extracting and printing all property values of an entity could be written like this:

Entity entity = getEntity();

MetaEntity.INSTANCE.getProperties().stream()
                                   .map( StaticProperty::getValue )
                                   .forEach( System.out::println );

When accessing the values of Property Chains please note the following:

  • Chain resolution of non-Optional chains ends at null values and also will be returned as the result. Client code thus has to handle those situations accordingly.

  • Nested structures with multiple collections in between might result in large final Lists of data. For example, if you have an Aspect with a list of 1000 or more measurements and each measurement again contains a list of a few hundred data samples, resolving the chain to the data samples might easily give you results into the millions.

Advanced Usage

Writing Generic Code

So far our examples have been working in an ad-hoc fashion on our Aspect data which already gives you elegant, type safe and intent-explaining code. Still, such code partially also could be written directly on the domain model. Let’s now take a look at functionality, that is very complicated to write without the support of a Meta Model at hand.

We’re talking about generic code, i.e. code that knows how to work with data and its properties, but doesn’t care, what data it exactly is.

Let’s define a simple, but realistic example of such a generic piece of code.

Assume you’re modeling data structures that handle all sorts of technical components, maybe a device that is broken up into its sub-components. Each sub-component is modeled with different properties, but they all share one thing: their unique identifier.

For the application we’re developing it now is regularly needed, to retrieve a set of components and index them by their unique id, something like this:

Map<Integer, SubComponent> =
    subComponents.stream()
                 .collect( Collectors.toMap( SubComponent::getId, Function.identity() ) );

However, we don’t only have this single kind of SubComponent. We have multiple, on different levels of our domain model. Additionally, we don’t have a clean inheritance hierarchy where we can assume that the unique id is always an Integer and always within the property getId(). If we still want to be able to write a generic solution to this requirement, our Static Meta Model comes to the rescue! All we need to know is any property (or property chain) which we use to perform the indexing:

public <K, V> Map<K, V> index( List<V> components, StaticProperty<V, K> uniqueId ) {
    return components.stream()
                     .collect( Collectors.toMap( uniqueId, Function.identity() ) );
}

...

Map<Integer, SubComponent> componentsById =
    index( subComponents, MetaSubComponent.ID );

Now you have an algorithm that will work on any Aspect or Entity which does have an unique identifier.

Maybe you already spotted how easily such generic code can be pushed even further. Assume, that for some parts of the application you don’t want to index the component itself, but one of its properties (maybe the OEM). Again, using properties such an extension is elegant and simple:

public <K, T, V> Map<K, T> index( List<V> components, StaticProperty<V, K> uniqueId, Function<V, T> valueMapper ) {
    return components.stream()
                     .collect( Collectors.toMap( uniqueId, valueMapper ) );
}

...

Map<Integer, SubComponent> componentsById =
    index( subComponents, MetaSubComponent.ID, Function.identity() );
Map<Integer, String> componentOemsById =
    index( subComponents, MetaSubComponent.ID, MetaSubComponent.OEM );

Filtering and Sorting with Properties

In the same way that properties can be used to access data, they can also be used to filter and sort data.

Filtering

To filter elements based on their properties (or nested properties, via a Property Chain), the convenient builder PropertyPredicates exists. It provides the following entry points:

Entry Point Types Description

on( PropertyAccessor<C, T> property )

C any containing type
T any non-container property type

For simple predicates on single-valued properties

matchOn( PropertyAccessor<C, T> property )

C any containing type
T any type that extends a CharSequence

Used to build contains and RegEx predicates

compareOn( PropertyAccessor<C, T> property )

C any containing type
T any type that extends a Comparable

Used to build (partial) range predicates like greaterThan or withinClosed on comparable properties

onOptional( PropertyAccessor<C, T> property )

C any containing type
T any Optional type

For simple predicates on Optional values.

onCollection( PropertyAccessor<C, T> property )

C any containing type
T any type that extends a Collection

Used to build predicates on collections, like contains or containsAllOf

Let’s take a look and some examples using those builders:

List<Movement> movements = retrieveMovements();

// find all that are currently moving
List<Movement> currentlyMoving =
    movements.stream()
             .filter( PropertyPredicates.on( MetaMovement.IS_MOVING ).isEqualTo( true ) )
             .toList();

// find all that have a speed within the range 10km/h <= speed <= 30km/h
List<Movement> withinSpeedRange =
    movements.stream()
             .filter( PropertyPredicates.compareOn( MetaMovement.SPEED ).withinClosed( 10.0f, 30.0f ) )
             .toList();

Again assuming our example with a component and its subcomponents, let’s filter on collection-valued properties:

List<Components> components = retrieveComponents();

var componentOem = PropertyChain.fromCollection( MetaComponent.SUB_COMPONENTS )
                                .to( MetaSubComponent.OEM );
List<Components> withOemAcme =
    components.stream()
              .filter( PropertyPredicates.onCollection( componentOem ).contains( "ACME" ) )
              .toList();
To conveniently build even more complex predicates with AND or OR semantics you can for example use the Predicates utility from the Vavr library.

Sorting

Property-based sorting doesn’t require any special utilities at all, due to the fact that properties already implement the Function interface and thus can directly be used as a keyExtractor with Comparator.comparing():

List<Movement> bySpeedAscending =
    movements.stream()
             .sorted( Comparator.comparing( MetaMovement.SPEED ) )
             .toList();