Skip to content

uml4net.xmi.project

samatstarion edited this page Jan 6, 2025 · 4 revisions

Introduction

The uml4net.xmi project/library is used to read an XMI document of a UML model and return a fully dereferenced object graph of its contents. The entry point of the library is the XmiReaderBuilder which returns an IXmiReader that is used to read an XMI document.

The following code snippet demonstrates how the XmiReaderBuilder is used to read an XMI document:


Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddSerilog();
});

var rootPath = <...path to folder where external references are stored>

var reader = XmiReaderBuilder.Create()
    .UsingSettings(x => x.LocalReferenceBasePath = rootPath)
    .WithLogger(loggerFactory)
    .Build();

var xmiReaderResult = reader.Read(<...path to XMI file...>);

The XmiReaderResult contains all Packages, inlcuding the root Package, that were read. The root Package is the Package, Model or Profile that is at the root of the XMI document being read.

The .UsingSettings method is used to configure the settings. The following can be set:

  • LocalReferenceBasePath: the path to the folder where external references (referenced UML models) are available.
  • PathMap: a dictionary of path maps that are using in the XMI documents. a path map act as virtual path or aliase that maps to physical file locations

The use of PathMap is demonstrated with the following code snippet:

var reader = XmiReaderBuilder.Create()
    .UsingSettings(x =>
        x.PathMap = usingSettings ? new Dictionary<string, string> { ["pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml"] = <...path to physical file location...> } : [])
    .WithLogger(loggerFactory)
    .Build();

var xmiReaderResult = reader.Read(<...path to XMI file...>);

XmiReaders

For each concrete UML class an IXmiElementReader is code-generated. This IXmiElementReader is responsible for reading an XML fragment that describes that IXmiElement and its contained IXmiElements. The read method of the IXmiElementReader class returns an instance of the IXmiElement that it is responsible for. For each owned attribute (i.e. each contained property) the IXmiElementReaderFacade is responsible for resolving the correct IXmiElementReader.

The IXmiElementReaderFacade and the IXmiElementReader implementations are all code-generated using the uml4net libraries and are stored in the AutoGenXmiReaders project folder.

Reading starts at the XmiReader.Read method where a Model, Package or StereoType are read. The code snippet below from XmiReader.Read shows the pattern that is repeated accross all the generated IXmiElementReader implementations.

private void Read(Stream stream, string documentName, XmiReaderResult xmiReaderResult, bool isRoot)
{
    var settings = new XmlReaderSettings();

    stream.Seek(0, SeekOrigin.Begin);

    using (var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true))

    using (var xmlReader = XmlReader.Create(reader, settings))
    {
        var defaultLineInfo = xmlReader as IXmlLineInfo;

        while (xmlReader.Read())
        {
            if (xmlReader.NodeType == XmlNodeType.Element)
            {
                switch (xmlReader.LocalName)
                {
                    case "Package":
                        var package = (IPackage)xmiElementReaderFacade.QueryXmiElement(xmlReader, documentName, xmlReader.NamespaceURI, cache, this.loggerFactory, "uml:Package");
                        xmiReaderResult.Packages.Add(package);

                        if (isRoot)
                        {
                            xmiReaderResult.Root = package;
                        }

                        break;
                    case "Model":

                        var model = (IModel)xmiElementReaderFacade.QueryXmiElement(xmlReader, documentName, xmlReader.NamespaceURI, this.cache, this.loggerFactory, "uml:Model");
                        xmiReaderResult.Packages.Add(model);

                        if (isRoot)
                        {
                            xmiReaderResult.Root = model;
                        }

                        break;
                    case "Profile":
                        var profile = (IProfile)xmiElementReaderFacade.QueryXmiElement(xmlReader, documentName, xmlReader.NamespaceURI, this.cache, this.loggerFactory, "uml:Profile");
                        xmiReaderResult.Packages.Add(profile);

                        if (isRoot)
                        {
                            xmiReaderResult.Root = profile;
                        }

                        break;
                    default:
                        this.logger.LogWarning("XmiReader: {LocalName} at line:position {Line}:{Position} was not read", xmlReader.LocalName, defaultLineInfo.LineNumber, defaultLineInfo.LinePosition);
                        break;
                }
            }
        }
    }

    this.TryResolveExternalReferences(xmiReaderResult, documentName);

    if (isRoot)
    {
        this.assembler.Synchronize();
    }
}

External References, the ExternalReferenceResolver and the Assembler

The XmiReader is responsible for starting the process to resolve external references and to start the Assembler.Synchronize process. External references are those XmiElements that are part of another XMI document. A good example is the PrimitiveTypes.xmi document which is referenced from the UML.xmi document. The PrimitiveTypes.xmi file contains primitive types such as Boolean and Integer that are used in the UML.xmi file.

The ExternalReferenceResolver can resolve all external references that are available at the LocalReferenceBasePath that are referenced from any XMI model file that is found, also during the process where external references are resolved.

The Assembler works together with the XmiElementCache to dereference an object graph. This is necessary when the reference properties of the IXmiElement instances (the UML classes) are not pointers to other instances, but are encoded in the SingleValueReferencePropertyIdentifiers or MultiValueReferencePropertyIdentifiers AND all the instances are stored in the IXmiElementCache.

The Synchronize method that iterates through all the entries in the SingleValueReferencePropertyIdentifiers and MultiValueReferencePropertyIdentifiers of all the XmiElement that are stored in the XmiElementCache and updates the single-valued and multi-valued properties of these instances. Both the SingleValueReferencePropertyIdentifiers and the MultiValueReferencePropertyIdentifiers properties are Dictionaries where each entry is a key-value pair that contains the naem of the property, the values are either single or multiple unique identifiers, the XmiElement.XmiId property, of other XmiElement instances.