Serializing and materializing MSE files with the Fame meta-engine

MSE is the Moose textual format for serializing and materializing models and meta-models. It is a simple format that essentially looks like nested Lisp lists or Smalltalk literal arrays. MSE is supported through the Fame meta-engine, an engine originally created by Adrian Kuhn.

A typical example of a model is a code model that conforms to the FAMIX language independent meta-model. Such a model can be produced by an external tool like VerveineJ, or inFamix and then can be imported into Moose.

How does this work?

To go into details, let's pick a simple non-FAMIX example. Often, people that heard of FAMIX tend to think that Moose handles only such models. However, MSE is a general format being able to handle any model. Here is a different example:

mse := '(
   (LIB.Library
       (librarian
           (LIB.Person
               (name ''The librarian'')))
       (books
           (LIB.Book
               (title ''Deep into Pharo'')
               (authors (ref: 1) (ref: 2) (ref: 3) (ref: 4)))
           (LIB.Book
               (title ''Pharo by Example'')
               (authors (ref: 5) (ref: 3) (ref: 6) (ref: 7)))
   (LIB.Person (id: 1)
       (name ''Alexandre Bergel''))
   (LIB.Person (id: 2)
       (name ''Damien Cassou''))
   (LIB.Person(id: 3)
       (name ''Stephane Ducasse''))
   (LIB.Person(id: 4)
       (name ''Jannik Laval''))
   (LIB.Person(id: 5)
       (name ''Andrew Black''))
   (LIB.Person(id: 6)
       (name ''Oscar Nierstrasz''))
   (LIB.Person(id: 7)
       (name ''Damien Pollet'')))))'.

In this case, we deal with a library model that handles entities like Book or Person. To load this model into Moose, you need to have a meta-model first. For our case, the meta-model happens to exist already as a default example in the LIBRoot hierarchy that comes with the Moose image.

For example, if you want to materialize the above MSE string into Pharo objects, you can execute:

repository := LIBRoot mainLibrary model importString: mse.

The result is a Fame repository. The repository has two variables. The elements variable points to the model objects. The metamodel variable points to the meta repository.

In our example, we have 11 such objects as seen in the screenshot below (showing the result in GTInspector).

Inspector-repository.png

The objects are created with proper connections between them. The below visualization shows the connections for our case: each bubble is an object, and each connection represents a reference between two objects.

Library-connections.png

As mentioned before, the metamodel variable from a repository instance points to the so called meta repository. If we inspect this meta repository, we also see a kind of repository with 10 items.

Inspector-meta-repository.png

These items no longer represent the objects from our model. Instead they represent the types from our model. For example, we have a Library description and a Library.librarian property. These objects are used by the Fame engine to represent objects models internally and they provide the information necessary for handling the import-export logic.

But, wait a second. If a meta-repository is a repository, can we not serialize it in MSE? Thanks for asking. Of course, you can.

MSE is a generic format. This means that you can serialize meta-models, too. For example, our library meta-model can be exported easily by invoking exportString on the meta repository:

LIBRoot mainLibrary model metamodel exportString

Inspector-exportString.png

This functionality is particularly useful when dealing with models and associated meta-models that are provided by external tools. In this situation, the steps to follow are:

  1. Import the meta-model in a meta repository.
  2. Generate Pharo classes from the meta repository.
  3. Import the actual model.

Perhaps this sounds complicated, but in reality it is rather straightforward:

metaMse := LIBRoot mainLibrary model metamodel exportString.
tower := FMTower new.
tower metamodel importString: metaMse.
generator := FMDefaultCodeGenerator new.
generator visit: tower metamodel.
generator previewChanges.

The above script, loads the meta MSE and then invokes the code generator for it. For our example, we chose precisely the MSE file representing the Library meta-model. Executing the script reveals a browser offering a preview of the code generation.

Changes-preview.png

Once the code for the meta-model is generated, you can rebuild a meta repository and load the MSE file with the model. In our example, the meta-model consists of three classes LIBBook, LIBLibrary and LIBPerson. To build the repository, you can use the existing pragma processor:

pp := FMPragmaProcessor new.
pp queue: (Array with: LIBBook with:  LIBLibrary with:  LIBPerson).
pp run.
tower := pp asTower.

Afterwards, you simply load the MSE with the model objects as you did in the first example:

tower model importString: mse.

The Fame functionality is at the core of model manipulation in Moose, and through MSE it makes it possible to handle complicated models and meta-models. By default Moose focuses on code related meta-models, but the underlying functionality is generic.

For simple meta-models, a simple inspector and code browser can be enough. For more complicated meta-models, such as FAMIX, Moose offers a dedicated Moose Meta Browser that can help you deal with multiple facets of a meta-model including inheritance, derived properties (these are properties that do not have to be serialized), or declared types. For example, the screenshot below shows the browser displaying the details of a FAMIX entity.

Meta-browser.png

Meta-modeling can appear difficult at times, but as this post shows, if you put your mind to it, it does not have to be especially when you get an engine like Fame that is both compact and easy to use.

Posted by Tudor Girba at 13 October 2013, 11:36 pm with tags moose, tooling link
|