Skip to content
Georg Hinkel edited this page Sep 29, 2017 · 1 revision

The following instructions will describe how to set up a project with NMF and create a model transformation from state machines to Petri nets. Although the tutorial is specifically written for a usage in Visual Studio, the tutorial can be adapted to any IDE on the .NET platform. The tutorial also assumes that you have already started to create a metamodel and some instances of it in EMF, i.e. Eclipse.

If you get stuck at any point, there is a ready-made solution available on GitHub that can be just downloaded and tried. Furthermore, there is a YouTube video available demonstrating creating a new project, loading, altering and saving a model.

Create a project

NMF is a framework that can be easily installed through the NuGet Packagemanager. Therefore, first create a new project. In Visual Studio, click on File -> New -> Project, select a C# console application as project type and name it as you wish, though in the remainder we will assume the name NMFDemo. Note that NMF is generally not restricted to console applications nor to C#, you can use it in any .NET project.

To import NMF, go to Tools -> NuGet Package-manager -> Manage NuGet packages for this project and search for NMF. You should find the package NMFBasics. Install it by hitting the Install button while your project is selected. Alternatively, there is also a NuGet console at the bottom, where you can install NMF as follows:

PM> Install-Package NMF-Basics

NuGet will download the package for you together will all of its dependencies and add all the contained libraries as references into the current project. There is no strict 1:1-mapping from NuGet packages to libraries so there are multiple libraries being installed that may be not needed.

Import Metamodels from Ecore

Metamodels are at the core of any model-driven development process. Thus, as a first step, we will generate code in order to be able to load any models for a given metamodel in our .NET application. For this, the NuGet package NMF-Basics contains the console application Ecore2Code. After a restart of Visual Studio, NuGet will automatically add Ecore2Code to the Path variable used inside Visual Studio, so you can just use the NuGet Package-manager console. If run without any arguments, this application prints a help information showing its correct usage.

Now, use this tool to generate the code for the state machine metamodel and the Petri net metamodel. You can download these metamodels from our examples project8. First copy the metamodels into your project folder, then generate the code for them. The complete commandline for the latter is as follows:

PM> Ecore2Code -f -n NMFDemo.Metamodels -m fsm.nmf -o Metamodels\FiniteStateMachines fsm.ecore
PM> Ecore2Code -f -n NMFDemo.Metamodels -m pn.nmf -o Metamodels\PetriNets pn.ecore

The generated code now has to be added to your project. Thus, first display all files in the projects folder by clicking on Show All Files in the project explorer, then include the generated folder Metamodels and the generated NMeta metamodels into your project (right-click and Include In Project). As soon as the generated code is added to the project, it is already possible to programatically create and save models. However, the metamodel is not yet registered and thus no models can be loaded. To register the metamodel, we first need to include the NMeta metamodel in the assembly as an embedded resource and then registers the metamodel. To make the metamodel an embedded resource, simply change its Build Option to Embedded Resource in the properties view while the metamodel is selected.

The metamodel registration is done through an assembly-wide attribute, which can be specified anywhere in the project. The typical place for this registration, however, would be the AssemblyInfo.cs file in the properties folder. At the top of this file, add the following two lines:

[assembly: NMF.Models.ModelMetadata("http://github.com/NMFCode/Examples/FiniteStateMachines", "NMFDemo.fsm.nmf")]
[assembly: NMF.Models.ModelMetadata("http://github.com/NMFCode/Examples/PetriNets", "NMFDemo.pn.nmf")]

This is all there is, even if you compile your project not as an executable but as a reusable library. As a reason, when loading the serializer, NMF looks for these attributes in all assemblies referenced by the executing assembly and loads any metamodel registrations it can get.

Loading a model

In NMF, models are loaded by resolving their URI in a model repository. If the repository does not contain a model with the given URI, then the model is automatically loaded into the repository, provided NMF is able to locate it. Repositories are closed under cross-reference, meaning that all references to other model elements are always resolved within the repository or its parent repository. To create a repository, we simply need to create an object of type ModelRepository. With the default configuration, this repository is able to deserialize any models conforming to metamodels registered in referenced assemblies, as all repositories implicitly use the meta repository where the metamodels are loaded into.

var repository = new ModelRepository();
var model = repository.Resolve("Example.fsm");
var fsm = model.RootElements[0] as FiniteStateMachine;

For example, the code needed to load a model from the file Example.fsm representing a small order process is depicted above. Add these lines to the main method. You can now launch the application and validate that the model can be loaded successfully.

Incrementalization

The generated model representation classes for the metamodel support change notifications through the .NET de-facto standard interfaces INotifyProperty- Changed and INotifyCollectionChanged. Thus, the generated classes raise events whenever some properties have been changed or elements have been added to or removed from collections. NMF is able to combine these elementary change notifications to deduct when the value for a combined expression has changed. For example, let us analyze hubs in the finite state machines, i.e. states that have the maximum incoming transitions. A set of such states can be deducted through the analysis depicted below:

var stateHubs = from s in fsm.States
where s.Incoming.Count == fsm.States.Max(s2 => s2.Incoming.Count)
select s.Name;

Verify that the variable stateHubs is of type IEnumerable, i.e. a standard collection of strings. Now, we need to add a using statement at the top of the program file to the Linq implementation of NMF Expressions. Add the code below to the top of the program file.

using NMF.Expressions.Linq;

As a consequence, the Linq implementation of NMF Expressions is used and thus, the variable stateHubs has the type IEnumerableExpression. This adds a method to obtain an incrementalized version of the query through the AsNotifiable method.

stateHubs.AsNotifiable().CollectionChanged += (o,e) => {
  if (e.NewItems != null)
    for (string name in e.NewItems) { Console.WriteLine("{0} is a new hub", name); }
  if (e.OldItems != null)
    for (string name in e.OldItems) { Console.WriteLine("{0} is no longer a hub", name); }
};

To verify the change propagation, visualize changes made to the state hub analysis through the code shown above. Normally, the method AsNotifiable is a very expensive operation, thus one would save the return value.

var checkStock = fsm.States[1];
checkStock.Outgoing.Add(new Transition() {
  Input = "items are for free",
  Target = fsm.States[2]
});

Add some change operations after registering the handler, step through the console application and see how new hubs are immediately shown in the console. For example, you can use the code listed above to create a new transition to skip payment when items of the order process are for free. As a consequence of this change, a message will pop up in the console that a new hub has been detected directly afterwards Line 2-5 have been executed.

Creating a model transformation

Now, we are going to transform the state machine model into a different model, for instance in a Petri net.

At first, we need to add the libraries to run model transformations in NTL. The easiest way to get them is to download them as another NuGet package. Install NMF Transformations through the NuGet command NMF-Transformations or again through the GUI.

PM> Install-Package NMF-Transformations

A model transformation in NMF Transformations is a special class, inheriting from ReflectiveTransformation. Thus, create a new class by adding a new class to the project. Download the model transformation FSM2PN from finite state machines to Petri nets from the examples page and copy its contents into the new file.

To run this model transformation, we need to instantiate the model transformation, initialize it and run it. The initialization can be reused for multiple passes of a model transformation, in case the model transformation initialization is costly. To apply the model transformation, we need to pass the source and target model type as generic parameters. The transformation then selects an appropriate rule to start with and traverses the transformation through the rule dependencies. Thus, we can ask the transformation to transform states or entire state machines.

var transformation = new FSM2PN();
var context = new TransformationContext();
var petriNet = TransformationEngine.Transform<StateMachine, Net>(fsm, context);

After the transformation, the context object can be used for tracing purposes.

Saving the result to a file

In NMF, the serialization information of model elments is attached directly to the model representation classes. The NMF serializer uses this information and interprets how the model should be serialized to XMI. To serialize the Petri net, we simply save it into our model repository (or create a new one). Any referenced model element already contained in another existing file is referenced through a fully qualified reference.

repository.Save(petriNet, "Example.pn");

To save a model element to a file, it is sufficient to call the Save method on the repository such as shown as above. Verify that you can open this file in Eclipse.