Skip to content

Generating uml4net

samatstarion edited this page Oct 11, 2026 · 1 revision

Introduction

The classes, interfaces and enumerations of uml4net, and its XMI readers and writers, are not written by hand. They are generated from the UML 2.5.1 model with uml4net itself: the model is read with the XmiReader, and the code is written by the generators of the uml4net.CodeGenerator project using Handlebars.Net templates and the helpers of the uml4net.HandleBars project.

This page describes that pipeline and the helpers that exist to support it.

The helpers on this page write code that is tied to the types and naming of uml4net, such as IContainerList<T>, the [Property] attribute and the XmiElementReader. They are not intended for use in other code generators and can change with any release. The helpers that can be used in any template are documented in UML Handlebars Helpers.

From model to code

Each generator registers the helpers and the templates it needs, queries the classes or enumerations from the model, and writes one file for each of them.

Generator Templates Generates Destination
CorePocoGenerator core-poco-interface-template an interface for each class of the model uml4net/AutoGenInterfaces
core-poco-class-template a class for each concrete class of the model uml4net/AutoGenClasses
core-enumeration-template an enum for each enumeration of the model uml4net/AutoGenEnumeration
XmiReaderGenerator xmi-reader-template an XMI reader for each concrete class uml4net.xmi/AutoGenXmiReaders
XmiElementReaderFacade-template the facade that selects the reader of a class uml4net.xmi/AutoGenXmiReaders
XmiWriterGenerator xmi-writer-template an XMI writer for each concrete class uml4net.xmi/AutoGenXmiWriters
XmiElementWriterFacade-template the facade that selects the writer of a class uml4net.xmi/AutoGenXmiWriters
ExtensionGenerator extension-core-poco-class-template, extension-xmi-reader-template, extension-reader-facade-template the classes and readers of a tool specific extension, from the model of that extension uml4net.xmi.Extensions.EnterpriseArchitect

The ExtensionGenerator derives from the CorePocoGenerator and also uses its interface and enumeration templates.

Running the generators

The generators are run from the test fixtures in the Generators folder of the uml4net.CodeGenerator.Tests project. A fixture generates the code into a sub folder of the test output directory, such as _uml4net.AutoGenClasses, and compares the code of a number of classes with the files in the Expected folder of the test project. The generated code is copied to the destination project once the tests pass.

The XmiWriterGeneratorTestFixture has an explicit test that writes the XMI writers to the AutoGenXmiWriters folder of the uml4net.xmi project directly.

The Code Generation Tutorial explains this way of working step by step.

Helpers for the classes and interfaces

Properties

Helper Input Writes
Property.WriteForInterface Context, an IProperty the declaration of the property in an interface: the visibility, new for a property that redefines another property, the type, the name and the accessors
Property.WriteForClass Arguments: the IProperty and the IClass that is generated the implementation of the property in the class that is generated
Property.WriteAsDerivedCompositeUnion Arguments: the IProperty and the IClass that is generated a derived property that returns the union of the values of all composite properties that the class holds, which is the shape of Element::ownedElement

Property.WriteForInterface selects the type as follows:

The property Is written as
is a containment IContainerList<IType>, IReadOnlyList<IType> when it is derived or a derived union
is a reference with an upper bound larger than 1 List<IType>, IReadOnlyList<IType> when it is derived, a derived union or read-only
is typed by a DataType with an upper bound larger than 1 List<T>
is a nullable value type T?
is any other property T for a DataType, IType for a reference

A property that is read-only or derived gets { get; }, any other property { get; set; }. An s is appended to the name of a property that has the name of the class that owns it.

public IContainerList<IActivityEdge> Edge { get; set; }
public IReadOnlyList<IElement> OwnedElement { get; }

Property.WriteForClass writes, depending on the property:

  • a lazily created ContainerList for a containment. The ContainerList is given the actions that set and clear the owner end of the composite association, such as Generalization::specific for Classifier::generalization, and the upper bound of a single-valued composite property.
  • an explicit interface implementation that forwards to the redefining property, for a property that is redefined in the context of the class.
  • accessors that throw an InvalidOperationException, for a redefined property that cannot be forwarded: a collection that is redefined by a separately stored collection with a narrower type.

Property.WriteAsDerivedCompositeUnion does not know which property it is used for. The template selects the property with Property.XmiIdEquals:

{{#if (Property.XmiIdEquals property "Element-ownedElement") }}

Decorators

The decorator helpers write the C# attributes that record the information of the model on the generated classes and interfaces. They are registered with RegisterDecoratorHelper.

Helper Input Writes
Decorator.WriteClassAttribute Context, an IClass [Class(xmiId: ..., isAbstract: ..., isFinalSpecialization: ..., isActive: ...)]
Decorator.WritePropertyAttribute Context, an IProperty [Property(xmiId: ..., aggregation: ..., lowerValue: ..., upperValue: ..., isOrdered: ..., isReadOnly: ..., isDerived: ..., isDerivedUnion: ..., isUnique: ..., defaultValue: ..., isOwnerEnd: ...)] and a new line. An unlimited upper bound is written as int.MaxValue
Decorator.WriteImplementsAttribute Context, an IProperty that is owned by an IClass [Implements(implementation: "IElement.OwnedComment")] and a new line: the interface of the owner and the name of the property
Decorator.WriteRedefinedPropertyAttribute Context, an IProperty [RedefinedProperty(propertyName: "...")] with the XmiId of each property that is redefined, each on a line. Nothing for a property that does not redefine
Decorator.WriteRedefinedByPropertyAttribute Arguments: the IProperty and the IClass that is generated [RedefinedByProperty("IClassifier.TemplateParameter")] when the property is redefined in the context of the class. Nothing in any other case
Decorator.WriteSubsettedPropertyAttribute Context, an IProperty [SubsettedProperty(propertyName: "...")] with the XmiId of each property that is subsetted, each on a line. Nothing for a property that does not subset

Known issue: Decorator.WriteImplementsAttribute is registered twice in the DecoratorHelper. The second registration replaces the first and is the one that is described here.

Generalization

The generalization helpers write the base list of a type declaration. Their input is the Context, an IClass. They are registered with RegisterGeneralizationHelper.

Helper Writes Used by
Generalization.Interfaces : IA, IB, the interfaces of the superclasses of the class. Nothing for a class without superclasses core-poco-interface-template
Generalization.InterfaceImplementationAndInterfaces : IA, IB, the interfaces of the superclasses and of the interfaces that the class realizes. Nothing for a class that has neither not used by a template
Generalization.Classes : IName, the interface of the class itself not used by a template

Known issue: Generalization.Classes is meant to also write the name of the superclass. The check on the generalizations of the class is inverted: the superclass is not written for a class that has one, and an exception is thrown for a class that has none.

Operations

The operation helpers write an operation of the model as a C# method. They take an IOperation as Argument and are registered with RegisterOperationHelper. None of them is used by a template at the moment.

Helper Arguments Writes
Operation.WriteForPOCOInterface the IOperation the method of the interface: ReturnType Name(parameters);, with new for an operation that redefines an operation of the same name. void is written for an operation without a typed return parameter
Operation.WriteForPOCOExtend the IOperation the signature of the extension method that holds the implementation: internal static ReturnType ComputeNameOperation(this IOwner ownerSubject, parameters). The method is named ComputeRedefinedNameOperation for an operation that redefines an operation of the same name
Operation.WriteForPOCOClass the IOperation and the IClass that is generated the method of the class, which calls the extension method. For an operation that is redefined in the context of the class, an explicit interface implementation that calls the redefining operation

Helpers for the XMI readers

Helper Arguments Writes
Property.WriteXmlAttributeForXmiReader the IProperty and the IClass that is generated the code that reads the property from an XML attribute
Property.WriteXmlElementForXmiReader the IProperty, the IClass that is generated and, optionally, true when the reader of an extension is generated the case that reads the property from a child XML element: a contained element, a reference or a value. A single-valued composite property that is given more than once is reported and the first value is kept
Class.WriteDerivedPropertyCasesForXmiReader the IClass that is generated the case labels that skip the serialized values of the derived, derived union and read-only properties of the class. XMI 2.5.1 clause 7.8.10 allows derived data to be serialized; uml4net computes these values and does not read them. Nothing is written for a class without such properties

Helpers for the XMI writers

Each of the helpers takes the IProperty, the IClass that is generated and, optionally, true to write the asynchronous variant of the code.

Helper Writes
Property.WriteXmlAttributeForXmiWriter the code that writes the property as an XML attribute: a single value of a primitive type or an enumeration, and a single reference to an element of the same document. A value is written when it is mandatory, or when it is set and differs from the default of the metamodel
Property.WriteXmlElementForXmiWriter the code that writes the property as child XML elements: contained elements, multiple references, a reference to an element of another document, and multiple values of a primitive type
Property.WriteCanonicalXmlElementForXmiWriter the code that writes the property for Canonical XMI (XMI 2.5.1 Annex B)
{{ #Property.WriteXmlAttributeForXmiWriter this.Property this.Class }}
{{ #Property.WriteXmlAttributeForXmiWriter this.Property this.Class true }}

For NamedElement::name the code that is written contains, respectively:

xmlWriter.WriteAttributeString("name", element.Name);
await xmlWriter.WriteAttributeStringAsync(null, "name", null, element.Name);

The owner end of a composite association is not written by any of these helpers. It is implied by the nesting of the XML elements, and the generated classes set it when an element is added to a ContainerList.

The reader and writer helpers throw a NotSupportedException that names the class and the property for a property that they cannot handle.

Helpers of the uml4net.CodeGenerator project

The ExtensionGenerator registers a number of helpers that are defined in the uml4net.CodeGenerator project itself, in the internal ExtensionHelpers class. They are not part of the uml4net.HandleBars library.

Helper Purpose
Generalization.Interfaces replaces the helper of the same name of the uml4net.HandleBars library, and adds uml4net.Packages.IPackage for the Extension class
ExtensionHelper.IsExtensionClass queries whether the class in the context is the Extension class
Class.QueryAllNonDerivedNonReadOnlyNonAssociationProperties queries the properties of a class that are not derived, not a containment and not part of an association
Class.QueryAllAssociationPropertiesExceptInterfaces queries the properties of a class that are not derived and part of an association, and that are not typed by an interface
Class.DoesExtensionReferencesXmiElement queries whether a class has an association property that is typed by the XmiElement interface
Property.WriteXmlAttributeForExtensionReader writes the code that reads a property of an extension from an XML attribute
Property.WriteXmlReferenceForExtensionReader writes the case that reads a property of an extension from a child XML element

See also

Clone this wiki locally