Manipulating Models

From nUML

Contents

Introduction

OMG's Standards

OMG's Unified Modeling Language™ (UML) establishes a set of widely accepted conventions for representing concepts related to object oriented analysis and design (OOAD). nUML provides the NUML.Uml2 library for UML, which aims at being compliant with the version 2.0 of the standard (see Unified Modeling Language (UML), version 2.0).

Another standard from the OMG is XML Metadata Interchange™ version 2.0 (XMI), which is partially supported by nUML and provided by the NUML.Xmi2 library. XMI specifies a way to serialize models using XML, enabling model interchange amongst different tools.

Models versus Diagrams

When we talk about UML, we usually think about boxes and lines. These boxes and lines represent different concepts from the OOAD, such as Class and Association, and these representations are expressed in a diagram.

But when we talk about programmatic manipulation, the position and size of the boxes don't matter, nor the thickness and color of the lines; the interesting bites are the concepts represented by these images. The set of concepts represented by these drawings is what we call model, and it is just the design that the diagram author wanted to represent graphically.

Programmatic Model Creation

Using the classes and interfaces from NUML.Uml2 we can create and modify models from our computer programs.

The namespace NUml.Uml2 provides an interface for each metaclass defined in the standard (in order to distinguish user classes, for example Person, Client, Bank, from the classes defined by the UML, the latter are called metaclasses). In each of these interfaces the methods and properties specified in UML 2.0 standard are defined; therefore, an excellent source of documentation is said standard, where all the elements of the UML as well as their relations are perfectly explained.

Besides these interfaces, the library exposes a class named Create that allows the creation of objects that implement these interfaces. For each metaclass defined by the UML there is a corresponding method that allows the creation of instances. For example, to create a UML package, all you have to do is this:

NUml.Uml2.Package mypkg = NUml.Uml2.Create.Package();

Once we obtained a reference to an object, it is possible to use its properties and methods in order to modify it; for example:

mypkg.Name = "SomePackageName";

assigns the name SomePackageName to the package we just created.

Relations between objects are also managed through properties. For example, to add a class to the set of owned types of a package, we can do this:

// create a new Class element
NUml.Uml2.Class cls = NUml.Uml2.Create.Class();
cls.Name = "ClassName";

// add it to the package
mypkg.OwnedType.Add(cls);

// tell the class that it belongs to the package
cls.Package = mypkg;

XMI Serialization

Models represented by objects from the NUML.Uml2 library can be serialized to XMI 2.0 by means of the serialization mechanism provided by NUml.Xmi2 and NUml.Uml2.Serialization.

The XMI serialization library was designed to handle objects from several assemblies serialized in several files. This flexibility is paid partly with greater API complexity, but having these features in mind this complexity makes sense.

The serialization process is lead by an instance of the class SerializationDriver.

NUml.Xmi2.SerializationDriver ser = new NUml.Xmi2.SerializationDriver();

A serialization driver cannot do much by itself; it needs specialized serializators, actually one for each library to be serialized. The UML library provides a serializador, whose full name is NUml.Uml2.Serialization.Serializer. In order to add serializers to the serialization driver the method AddSerializer must be used:

ser.AddSerializer(new NUml.Uml2.Serialization.Serializer());

Once we have built our serializer, we can use any of the different overloads of the Serialize method in order to generate an XMI file containing our model:

ser.Serialize(mypkg, System.Console.OpenStandardOutput(), "http://just_a_sample/");

This last call deserves an explanation. The method Serialize has several overloads, which is handy because it adjusts to the different uses that we may need. In this case we are providing the model that we want to serialize (mypkg), the standard output as a Stream, and string. This last parameter serves to identify to the model domain, that in this case represents the given stream. As it was already mentioned, the library allows handling several files simultaneously; each file is a model domain, and needs its own URI. In the case of regular files, their location are their URIs, but as in this case we are using a Stream so we must provide an identifier for the model domain.

The complete program can be compiled with the following command line (assuming that all the lines of code have been added to the method Main):

mcs -r:NUML.Xmi2.dll -r:NUML.Uml2.dll -out:test.exe Test.cs

when we run the project, with

mono test.exe

we get this:

<?xml version="1.0" encoding="utf-8"?>
<xmi:XMI xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmi:version="2.1"
         xmlns:uml="http://schema.omg.org/spec/uml/2.0/uml.xmi"
         xmlns:xmi="http://www.omg.org/XMI">
  <uml:Package xmi:id="99a3de9-4561-4031-b560-f7044042ce3" name="SomePackageName">
    <ownedType xmi:id="6ea198-50bb-4859-b72c-5c4b36dc1fd2" xsi:type="uml:Class" name="ClassName" />
  </uml:Package>
</xmi:XMI>

The values of the xmi:id attributes will likely be different for each execution of the program.

XMI Deserialization

We've just seen how to serialize an object graph to file. The inverse operation is also possible, and can be achieved using some of the overloads of the Deserialize method. For example:

public class TestDeserialization
{
  public static void Main(string[] args)
  {
    NUml.Xmi2.SerializationDriver ser = new NUml.Xmi2.SerializationDriver();
    ser.AddSerializer(new NUml.Uml2.Serialization.Serializer());
    System.Collections.IList deserialized = ser.Deserialize("test.xmi");
  }
}

In this case it's also necessary to initialize the serialization driver with the relevant serializers, according to the content that we want to deserialize. As result of the deserialization an IList is obtained, containing the list of root-level elements. For example, if we deserialize the following file:

 <?xml version="1.0" encoding="utf-8"?>
 <!-- save as test.xmi -->
 <xmi:XMI xmi:version="2.1"
        xmlns:xmi="http://www.omg.org/XMI"
        xmlns:uml="http://schema.omg.org/spec/uml/2.0/uml.xmi"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <uml:Package xmi:id="1" name="Mi_Paquete">
     <ownedType xmi:id="2" xsi:type="uml:Class" name="Mi_Clase" />
   </uml:Package>
 </xmi:XMI>

we will obtain an IList with a single element, in this case a UML Package.

Once we got the deserialized model, we have an object graph that can be navigated and modified using the same properties and methods that we saw in the previous section.

You can read models created with CASE and UML tools; further information on this topic can be found in Working with UML Tools.

Properties: Sets and Subsets

A very interesting characteristic of UML 2, available in nUML, is the composition of properties as sets and subgroups.

Perhaps this idea is easier to transmit explaining some concrete use, for example the UML 2 specification itself. In UML, all metaclases derive from Element. Element has an attribute called ownedElement, that contains all the elements that are property of the given element.

Element has also an attribute ownedComment, that contains all the comments that belongs to the element. What's interesting is that ownedComment is a subgroup of ownedElement, and therefore all the comments that belongs to ownedComment also appear in the collection ownedElement.

This can be demonstrated with this simple program:

// Test.cs - build with:  mcs -r:NUML.Uml2.dll -out:test.exe Test.cs
using System;
using UML = NUml.Uml2;
public class TestDeserialization
{
  public static void Main(string[] args)
  {
    UML.Class cls = UML.Create.Class();
    UML.Comment comment = UML.Create.Comment();
    comment.Body = "This is a comment.";
    cls.OwnedComment.Add(comment);
    Console.WriteLine("Does ownedComment contains the comment? " + cls.OwnedComment.Contains(comment));
    Console.WriteLine("Does ownedElement contains the comment? " + cls.OwnedElement.Contains(comment));
    foreach(UML.Element element in cls.OwnedElement)
    {
      UML.Comment c = element as UML.Comment;
      if(c != null)
      {
         Console.WriteLine("found a comment: " + c.Body);
      }
    }
  }
}

Remember that ownedComment and ownedElement are defined in Element but, as every UML metaclass derives from Element, they are also present in Class.

The output is:

Does ownedComment contains the comment? True
Does ownedElement contains the comment? True
found a comment: This is a comment.

UML defines several metaclasses and attributes, and most of which have subgroupping relations. We recommend consulting the standard to know all the possibilities, since in many opportunities they are very useful.