Repository navigation
Generating uml4net
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 theXmiElementReader. 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.
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.
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.
| 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
ContainerListis given the actions that set and clear the owner end of the composite association, such asGeneralization::specificforClassifier::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") }}
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.WriteImplementsAttributeis registered twice in theDecoratorHelper. The second registration replaces the first and is the one that is described here.
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.Classesis 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.
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 |
| 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 |
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.
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 |
- UML Handlebars Helpers: the helpers that can be used in any template
-
uml4net.CodeGenerator project: the generators, templates and the
ModelTransformer - Code Generation Tutorial: how to create a generator
- ContainerList: the collection that is generated for a containment
- uml4net project and uml4net.xmi project: the projects that hold the generated code
- Design Decisions
copyright @ Starion Group S.A.
- Home
- uml4net Tools
- Code Generation Tutorial
- Primitive Types Mapping
- Profiles and StereoTypes
- Code Generation Conventions
- UML Handlebars Helpers
- Generating uml4net
- Developers Experience
- SBOM
- Design Decisions