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.

