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
speedLimitWarningthis 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 |
|---|---|---|---|
|
The namespace of this Model Element |
|
|
|
The full URN of this Model Element |
|
|
|
The Characteristic Namespace of the Model |
|
|
|
The singleton instance of this Meta class. Used to access non-static information. |
|
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 |
|---|---|---|---|
|
The Java Class for this Model Element |
|
|
|
The URN of this Model Element |
|
|
|
The used Meta Model version |
|
|
|
The name of this Model Element |
|
|
|
All |
|
|
|
All |
|
|
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 |
|---|---|---|---|
|
The Java Class of this property including generics |
|
|
|
The Java Class of the element containing this property (e.g. an Aspect or an Entity) |
|
|
|
Only present on properties with container types like |
|
|
|
Whether this property has a complex type, e.g. an |
|
|
|
The property value of the given element instance |
The property type |
|
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 |
|---|---|---|---|
|
Updates the property value of the given element instance |
|
|
Types of Static Properties
Different kinds of Static Properties exist to reflect all possible elements within an Aspect Model:
| Property Type | Description | Generics |
|---|---|---|
|
A simple scalar property |
|
|
A property with a container type like |
|
|
A simple scalar property but with an additional method to get its |
|
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 Aspect → entity: Entity → subEntity: 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-
Optionalchains ends atnullvalues 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 |
|---|---|---|
|
|
For simple predicates on single-valued properties |
|
|
Used to build |
|
|
Used to build (partial) range predicates like |
|
|
For simple predicates on |
|
|
Used to build predicates on collections, like |
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();