Skip to content

meta-model

4 posts with the tag “meta-model”

Parametrics next generation

How do we represent the relation between a generic entity, its type parameters and the entities that concretize it? The Famix metamodel has evolved over the years to improve the way we represent these relations. The last increment is described in a previous blogpost. We present here a new implementation that eases the management of parametric entities in Moose.

The major change between this previous version and the new implementation presented in this post is this: We do not represent the parameterized entities anymore.

What’s wrong with the previous parametrics implementation?

Section titled “What’s wrong with the previous parametrics implementation?”

Difference between parametric and non-parametric entities

Section titled “Difference between parametric and non-parametric entities”

The major issue with the previous implementation was the difference between parametric and non-parametric entities in practice, particularly when trying to trace the inheritance tree. Here is a concrete example: getting the superclass of the superclass of a class.

  • For a non-parametric class, the sequence is straightforward: ask the inheritance for the superclass, repeat.

Getting super inheritances - Non-parametric entities.

  • For a parametric class (see the little code snippet below), there was an additional step, navigating through the concretization:
import java.util.ArrayList; "public class ArrayList<E> { /* ... */ }"
public MySpecializedList extends ArrayList<String> {}

Getting super inheritances - Parametric entities.

This has caused many headaches to developers who wanted to browse a hierarchy: how do we keep track of the full hierarchy when it includes parametric classes? How to manage both situations without knowing if the classes will be parametric or not? The same problem occurred to browse the implementations of parametric interfaces and the invocations of generic methods.

The previous implementation naming choices were a little complex to grasp and did not match the standard vocabulary, especially in Java:

  • A type parameter was named a ParameterType
  • A type argument was named a ConcreteParameterType

Each time there was a concretization, a parametric entity was created. This created duplicates of virtually the same entity: one for the generic entity and one for each parameterized entity. Let’s see an example:

public MyClass implements List<Float> {
public List<Integer> getANumber() {
List<Number> listA;
List<Integer> listB;
}
}

For the interface List<E>, we had 6 parametric interfaces:

  • One was the generic one: #isGeneric >>> true
  • 3 were the parameterized interfaces implemented by ArrayList<E>, its superclass AbstractList<E> and MyClass. They were different because the concrete types were different: E from ArrayList<E>, E from AbstractList<E>and Float.
  • 2 were declared types: List<Number> and List<Integer>.

When deciding of a new implementation, our main goal was to create a situation in which the dependencies would work in the same way for all entities, parametric or not. That’s where we introduce parametric associations. These associations only differ from standard associations by one property: they trigger a concretization.

Here is the new Famix metamodel traits that represent concretizations:

Class diagram for Parametric Associations

There is a direct relation between a parametric entity and its type parameters. A concretization is the association between a type parameter and the type argument that replaces it. A parametric association triggers one or several concretizations, according to the number of type parameters the parametric entity has. Example: a parametric association that targets Map<K,V> will trigger 2 concretizations.

The parametric entity is the target of the parametric association. It is always generic. As announced, we do not represent parameterized entities anymore. Coming back to the entities’ duplication example above, we now represent only 1 parametric interface for List<E>and it is the target of the 5 parametric associations.

This metamodel evolution is the occasion of another major change: the replacement of the direct relation between a typed entity and its type. This new association is called Entity typing.

Class diagram for Entity Typing

The choice to replace the existing relation by a reified association is made to represent the dependency in coherence with the rest of the metamodel.

With this new association, we can now add parametric entity typings.

In a case like this:

public ArrayList<String> myAttribute;

we have an “entity typing” association between myAttribute and ArrayList. This association is parametric: it triggers the concretization of E in ArrayList<E> by String.

Type parameters can be bounded:

public class MyParametricClass<T extends Number> {}

In the previous implementation, the bounds of type parameters were implemented as inheritances: in the example above, Number would be the superclass of T. Since this change, bounds were introduced for wildcards. We have now the occasion to also apply them to type parameters. In the new implementation, Number is the upper bound of T.

This diagram sums up the new parametrics implementation in Famix traits and Java metamodel. Please note that this is not the full Java metamodel but only a relevant part.

Class diagram for all changes

Should Concretization really be an association?

Section titled “Should Concretization really be an association?”

The representation of parametric entities is a challenge that will most likely continue as Famix evolves. The next question will probably be this one: should Concretization really be an association? An association is the reification of a dependency. Yet, there is no dependency between a type argument and the type parameter it replaces. Each can exist without the other. The dependency is in fact between the source of the parametric association and the type parameter.

With one of our previous examples:

public MySpecializedList extends ArrayList<String> {}

MySpecializedList has a superclass (ArrayList<E>) and also depends on String, as a type argument. However, String does not depend on E neither E on String.

The next iteration of the representation of parametric entities will probably cover this issue. Stay tuned!

Representation of parametrics

In Java generic types allow you to write a general, generic class (or method) that works with different types, allowing code reuse.

But their modeling and how it works can be difficult to understand. Let’s take an example.

public class ClassA<T>

Here, ClassA is a generic class because there is one generic type T. One can not use ClassA without specifying the generic type.

ClassA<Integer> class1 = new ClassA<Integer>;
ClassA<String> class2 = new ClassA<String>;

class1 and class2 are variables of type ClassA, but this time ClassA doesn’t have a generic type but String or Integer. So, how do we represent all that?

Modelisation_generic

We have 5 new traits in our meta-model :

  • TParametricEntity is used by all parametric entities. It can be a ParametricClass, ParametricMethod, and ParametricInterface.
  • TConcretisation allows one to have a link between two TParametricEntity. A TParametricEntity can have one or more concretizations with other TParametricEntity. Each TParametricEntity that is a concretization of another TParametricEntity has a genericEntity.
  • TConcreteParameterType for concrete parameters.
  • TGenericParameterType for generic parameters.
  • TParameterConcretisation is the same as TConcretisation but instead of two TParametricEntity it has TConcreteParameter and TGenericParameterType. TGenericParameterType can have one or more concretisations and TConcreteParameterType has generics.

A TParametricEntity knows its concrete and generic parameters.

ParameterType uses the TWithInheritance trait because in Java we can do the following: <T extends Object> and <? super Number>. For the first, it means that T can be all subclasses of Object and for the second, it means Number and all its superclasses or interfaces (Number, Object, Serializable). ParameterType also uses the TThrowable trait because we can have a genericException so ParameterType should be considered like it.

public interface GenericThrower<T extends Throwable> {
public void doThrow() throws T;
}

example

If we take the first class. We have a ParametricClass with one ParameterType name T.

{{ &#x27;classA<T>&#x27; | escape }}

For the second class, we have a class that extends a parametric class with one parameter named String. String here is a class. It is not a ParameterType anymore.

{{ &#x27;classB extends class<String>&#x27; | escape }}

So, what is the link between the two parametric classes and the parameters T and String?

concretization

We have here a Concretisation. ClassA with the parameter T has one concretization and the parameter T has one parameter Concretisation which is String.

If we take back our first example:

public class ClassA<T>
ClassA<Integer> class1 = new ClassA<Integer>
ClassA<String> class2 = new ClassA<String>

We have three ParametricClass, one ParameterType and two types (String and Integer). T is our ParameterType and has two ParameterConcretisations: String and Integer. We can say that T is generic and String and Integer are concrete because we know what they are: classes. ClassA with the ParameterType T (ClassA<T>) also has two concretizations. These are ClassA<Integer> and ClassA<String>. The three different classA know their parameters. T is in genericParameters. String and Integer are in concreteParameters.

A class is generic if it has at least one ParameterType. We can have concretization of a parametric class that is also generic. See the example below:

public class ParametricClass<T, V, K, Z>
public class ParametricClass2<Z> extends ParametricClass<String, Integer, Integer, Z>

The second ParametricClass has one ParameterType, so the class is generic. The four parameters (T, V, K, Z) have each a concretization (String, Integer, Integer, Z). Even if Z is still a ParameterType.

ParametricClass2 has for superclass ParametricClass, which has for generic entity ParametricClass with 4 ParameterTypes.

methodParametric

Let’s see what we have here. First of all, we recognize a ParametricClass with one ParameterType. This class has two methods. One is a regular method and the second is a parametricMethod. The first one isn’t generic because when the class is concretized, the ParameterType T will become String, Integer, Animals… and it will be the same for the method. The parameter of the first method depends on the class and this is not the case for the second method. That is why the second method is generic, not the first one.

public classA<T>
public classB extends classA<String>

This is how we can represent this in Pharo.

classAgen := FamixJavaParametricClass named:'ClassA'.
t := FamixJavaParameterType named:'T'.
classAgen addGenericParameter: t.
classAcon := FamixJavaParametricClass named:'ClassA'.
string := FamixJavaClass named:'String'.
classAgen addConcreteParameter: string.
FamixJavaConcretisation new concreteEntity: classAcon ; genericEntity: classAgen.
FamixJavaParameterConcretisation new concreteParameter: string ; genericParameter: t.
classB := FamixJavaClass named:'ClassB'.
FamixJavaInheritance new subclass: classB ; superclass: classAcon .

In this post, we have seen how generics types are modeled with VerveineJ and Moose for code analysis.

Connecting/Extending meta-models

Sometimes, a meta-model does not have all the information you want. Or, you want to connect it with another one. A classic example is linking a meta-model at an abstract level to a more concrete meta-model.

In this blog post, I will show you how to extend and connect a meta-model with another (or reuse a pre-existing meta-model into your own meta-model). We will use the Coaster example.

The Coaster meta-model is super great (I know… it is mine 😄 ). Using it, one can manage its collection of coasters.

However, did you notice that there is only one kind of Creator possible: the brewery. It is not great because some coasters are not created by breweries but for events. My model is not able to represent this situation. So, there are two possibilities: I can fix my meta-model, or I can extend it with a new concept. Here, we will see how I can extend it.

Extended Coaster meta-model

As presented in the above figure, we add the Events concept as a kind of Creator.

As a first step, we need the original Coaster meta-model generator loaded in our image. We can download it from my Coaster GitHub repository.

You should have a named CoasterCollectorMetamodelGenerator in your image. This is the generator of the original meta-model. We will now create another generator reusing the original one.

First, we create a new generator for our extended meta-model.

FamixMetamodelGenerator subclass: #CoasterExtendedMetamodelGenerator
instanceVariableNames: ''
classVariableNames: ''
package: 'CoasterCollector-ExtentedModel-Generator'

Then, we link our generator with the original one. To do so, we will use the submetamodels feature of the generator. We only have to implement the #submetamodels method in the class side of our new generator. This method should return an array including the generators of the submetamodels that we want to reuse.

CoasterExtendedMetamodelGenerator class >> #submetamodels
^ { CoasterCollectorMetamodelGenerator }

Finally, as for a classic meta-model generator, we define a package name and a prefix.

CoasterExtendedMetamodelGenerator class >> #packageName
^ #'CoasterExtended-Model'
CoasterExtendedMetamodelGenerator class >> #prefix
^ #'CCE'

Creating new concepts in the new meta-model is done following the same approach as for classic meta-model generator. In our example, we add the Event class. Thus, we create the method #defineClasses with the new entity.

CoasterExtendedMetamodelGenerator >> #defineClasses
super defineClasses.
event := builder newClassNamed: #Event.

To extend the original meta-model, we first need to identify the entities of the original meta-model we will extend. In our case, we only extend the Creator entity. Thus, we declare it in the #defineClasses method. To do so, we use the method #remoteEntity:withPrefix:. The prefix is used to allow multiple entities coming from different submetamodels but with the same name.

CoasterExtendedMetamodelGenerator >> #defineClasses
super defineClasses.
event := builder newClassNamed: #Event.
"Remote entities"
creator := self remoteEntity: #Creator withPrefix: #CC

We refer to a remote entity by sending #remoteEntity:withPrefix: to self and not using the builder. Indeed, the entity is already created.

Once the declaration done, one can use the remote entities as classic entities in the new generator. In our example, we will create the hierarchy between Creator and Event.

CoasterExtendedMetamodelGenerator >> #defineHierarchy
super defineHierarchy.
event --|> creator

Once everything is defined, as for classic generator, we generate the meta-model. To do so, execute in a playground:

CoasterExtendedMetamodelGenerator generate

The generation creates a new package with the Event entity. It also generates a class named CCEModel used to create an instance of our extended meta-model.

It is now possible to use the new meta-model with the Event concept. For instance, one can perform the following script in a playground to create a little model.

myExtendedModel := CCEModel new.
myExtendedModel add: (CCBrewery new name: 'Badetitou'; yourself).
myExtendedModel add: (CCEEvent new name: 'Beer party'; yourself)

We saw that one can extend a meta-model by creating a new one based on the pre-existing entities. It is also possible to connect two existing meta-models together.

To do so, let’s assume we have two existing meta-models to be connected. As an example, we will connect our coaster meta-model, with the world meta-model. The world meta-model aims to represent the world, with its continent, countries, regions and cities.

We will not detail how to implement the world meta-model. But the generator is available in my GitHub repository. The figure below illustrates the meta-model.

World meta-model

Connecting world meta-model with Coaster meta-model

Section titled “Connecting world meta-model with Coaster meta-model”

Our goal is to connect the coaster meta-model with the world meta-model. To do so, we will connect the country concepts of each meta-model.

Connected meta-model

As a first step, you should install both the coaster meta-model and the world meta-model. Again, both are available in my GitHub repository.

Then, we create a new meta-model generator that will perform the connection.

FamixMetamodelGenerator subclass: #ConnectMetamodelGenerator
instanceVariableNames: ''
classVariableNames: ''
package: 'Connect-Model-Generator'

To connect together the two meta-models, we must first declare them in our connector meta-model. To do so, we define the #submetamodels method.

ConnectMetamodelGenerator class >> #submetamodels
^ { WorldMetamodelGenerator . CoasterCollectorMetamodelGenerator }

And, as for every meta-model generator, we define a prefix and a package name.

ConnectMetamodelGenerator class >> #packageName
^ #'Connect-Model'
ConnectMetamodelGenerator class >> #submetamodels
^ #'CM'

Before creating the connection, we must declare, in the new meta-model, the entities that will be contected. To do so, we declare them as remoteEntity.

ConnectMetamodelGenerator >> #defineClasses
super defineClasses.
coasterCountry := self remoteEntity: #Country withPrefix: #CC.
worldCountry := self remoteEntity: #Country withPrefix: #W

Then, it is possible to connect the two entities as classic one.

ConnectMetamodelGenerator >> #defineRelations
super defineRelations.
coasterCountry - worldCountry

Build a model with two connected submetamodels

Section titled “Build a model with two connected submetamodels”

Once the generator is created, we can generate the connection by generating the new meta-model. To do so, execute in a playground:

ConnectMetamodelGenerator generate

Then, it is possible to create a model with all the entities and to link the two meta-models. In the following, we present a script that create a model.

"create the entities"
coaster1 := CCCoaster new.
coaster2 := CCCoaster new.
coaster3 := CCCoaster new.
coasterFranceCountry := CCCountry new name: #'France'; yourself.
coasterFranceCountry addCoaster: coaster1.
coasterFranceCountry addCoaster: coaster2.
coasterGermanyCountry := CCCountry new name: #'Germany'; yourself.
coasterGermanyCountry addCoaster: coaster3.
wFranceCountry := WCountry new name: #'France'; yourself.
wGermanyCountry := WCountry new name: #'Germany'; yourself.
continent := WContinent new name: #Europe; yourself.
continent addCountry: wFranceCountry.
continent addCountry: wGermanyCountry.
"connect CCountries to WCountries"
coasterFranceCountry country: wFranceCountry.
coasterGermanyCountry country: wGermanyCountry.
"put all entities into the same model"
connectedModel := CMModel new.
connectedModel addAll:
{ coaster1. coaster2 . coaster3 .
coasterFranceCountry . coasterGermanyCountry .
wFranceCountry . wGermanyCountry . continent }.

Based on the preceding model, it is possible to create query that will request the coaster and the world meta-model. For instance, the following snippet count the number of coasters by country in the Europe continent:

europe := (connectedModel allWithType: WContinent)
detect: [ :continent | continent name = #Europe ].
(europe countries collect: [ :eCountry |
eCountry name -> eCountry country coasters size ]) asDictionary

The coutry: and coutry methods are accessors that allow to set and recover one CCCountry (resp. WCountry) into a WCountry (resp. CCCountry). The accessors names are the same in both classes and were generated automatically from the declaration of the relationship in defineRelations (this is normal behaviour of the generator, not specific to using sub-models.

In this post, we saw how one can extend and connect meta-models using Famix Generator. This feature is very helpfull when you need to improve a meta-model without modifying it directly. If you need more control on the generated entities (e.g., name of the relations, etc.), please have a look at the create meta-model wiki page.

Coasters collection

I’m a coasters collector. I’m not a huge collector but I want to inventory them in one place. For sure, I can create a PostgreSQL database. But, at the same time, it appears that I can also design my collection using Moose.

So, you’re going to use a complete system analysis software to manage your coasters collection?

Exactly! And why? Because I think it’s simpler.

As for every software system, the first step is to design the model. In my case, I want to represent a collection of coasters. Let’s say a coaster is an entity. It can belong to a brewery or not (for example event coasters). A coaster also has a form. It can be round, squared, oval, or others. A Coaster can also be specific to a country. Because it is a collection, I can register coaster I own and other I do not. Finally, each coaster can have an associated image.

From this description of the problem, I designed my UML schema:

"coasters UML"

The most complicated part is done. We just need to implement the meta-model in Moose now 😄.

First of all, we’ll need a Moose 8 image. You can find everything you need to install Moose in the moose-wiki.

Ok! Let’s create a generator that will generate for us the meta-model. We only need to describe the meta-model in the generator. We will name this generator CoasterCollectorMetamodelGenerator.

FamixMetamodelGenerator subclass: #CoasterCollectorMetamodelGenerator
slots: { }
classVariables: { }
package: 'CoasterCollector-Model-Generator'

The generator needs to define two methods class side for the configuration:

  • #packageName defines where the meta-model will be generated
  • #prefix defines the prefix of each class when they are generated.

We used for #packageName:

CoasterCollectorMetamodelGenerator class >>#packageName
^ #'CoasterCollector-Model'

We used for #prefix:

CoasterCollectorMetamodelGenerator class >>#prefix
^ #'CC'

Now, we have to define the entities, their properties, and their relations.

A meta-model is composed of entities. In our case, it corresponds to the entities identified in the UML. We use the method #defineClasses to define the entities of our meta-model.

CoasterCollectorMetamodelGenerator>>#defineClasses
super defineClasses.
coaster := builder newClassNamed: #Coaster.
country := builder newClassNamed: #Country.
shape := builder newClassNamed: #Shape.
round := builder newClassNamed: #Round.
square := builder newClassNamed: #Square.
oval := builder newClassNamed: #Oval.
creator := builder newClassNamed: #Creator.
brewery := builder newClassNamed: #Brewery

We also need to define the hierarchy of those entities:

CoasterCollectorMetamodelGenerator>>#defineHierarchy
super defineHierarchy.
brewery --|> creator.
oval --|> shape.
square --|> shape.
round --|> shape

As we have defined the classes, we defined the properties of the entities using the #defineProperties method.

defineProperties
super defineProperties.
creator property: #name type: #String.
country property: #name type: #String.
coaster property: #image type: #String.
coaster property: #owned type: #Boolean

In this example, we did not use Trait already created in Moose. However, it is possible to use the Trait TNamedEntity to define that countries and creators have a name instead of using properties.

Finally, we defined the relations between our entities:

defineRelations
super defineRelations.
(coaster property: #shape) *- (shape property: #coasters).
(coaster property: #country) *- (country property: #coasters).
(coaster property: #creator) *- (creator property: #coasters)

Once everything is defined, we only need to use the generator to build our meta-model.

CoasterCollectorMetamodelGenerator generate

The generation creates a new package with our entities. It also generates a class named Model used to create an instance of our meta-model.

I have created my meta-model. Now I need to fill my collection. First of all, I will create a collection of coasters. To do so, I instantiate a model with: model := CCModel new. And now I can add the entities of my real collection in my model and I can explore it in Moose.

For example, to add a new brewery I execute: model add: (CCBrewery new name: 'Badetitou'; yourself).

The code is available on github.

Once I have created the collection, I can save it using the Moose export format (currently JSON and mse). To do so, I execute the following snippet:

'/my/collection/model.json' asFileReference ensureCreateFile
writeStreamDo: [ :stream | model exportToJSONStream: stream ]

Then I can select where I want to export my model.

To import it back into an image, I use the following code

'/my/collection/model.json' asFileReference
readStreamDo: [ :stream | model := CCModel importFromJSONStream: stream ]