Skip to content

Assembler

samatstarion edited this page Oct 4, 2026 · 2 revisions

Introduction

Assembler (interface IAssembler, namespace uml4net) turns the identifiers that the readers recorded on every XmiElement into object references, producing a fully connected object graph. It is the last step of a read: the XmiReader reads the root document, loads the external documents that are referenced from it, and then calls Assembler.Synchronize() once, over the whole XmiElementCache.

Why a separate step? An XMI document refers to elements by identifier (xmi:idref, href), and the element referred to may appear later in the same document or in another document altogether. Resolving at read time would require lookahead and multiple passes over the XML; recording the identifier and resolving once everything is in the cache is simpler and handles cross-document references for free.

What Synchronize does

For every element in the cache, in this order:

1. Single-valued references

For each entry propertyName → identifier in SingleValueReferencePropertyIdentifiers:

  • the identifier is looked up in the cache (see Identifiers below);
  • the target property is found by reflection: the property of the element's class decorated with [Property] whose name matches propertyName (case-insensitive) and whose type is assignable from the type of the referenced element;
  • the value is assigned, and the matching entry in UnresolvedReferences, the raw href element preserved while reading, is removed, since the reference will from now on be written from the property.

A reference whose target is not in the cache is logged as a warning and recorded as a resolution failure; the property stays null.

2. Multi-valued references

For each entry propertyName → identifiers in MultiValueReferencePropertyIdentifiers, the target List<T> property is found the same way, and every resolved element that is an instance of T is added to it. Identifiers that are not found, or refer to an element of an unexpected type, are recorded as failures and skipped.

3. Composite proxies

A composite property normally contains its elements, but XMI 2.5.1 clause 7.10.1 also allows a proxy: an element in the position of a contained element that only carries xmi:idref or href, pointing at the definition elsewhere (typically in another document). The readers record these in CompositeReferencePropertyIdentifiers together with their position.

For each proxy, in position order, the Assembler:

  • looks the definition up; if it is missing or of the wrong type the proxy is recorded as a failure and kept, so a later synchronization can still resolve it;
  • skips it when the definition is already in the list, or is the owner itself;
  • leaves it unresolved when the definition already has a different owner (AlreadyOwned); an element has exactly one owner, and the Assembler never takes an element away from the container that holds it;
  • leaves it unresolved when the property already holds as many values as the upper bound of its multiplicity (MultiplicityExceeded), read from the [Property] attribute, for example a proxy of a second value of the single-valued Constraint::specification;
  • otherwise inserts the definition into the IContainerList<T> at the proxy's position (adjusted for proxies before it that stayed unresolved), which sets its Possessor and its owner end through the ContainerList mechanism.

Only a definition that is a top-level element of the other document can be adopted this way; an element that is nested inside another owner there stays with that owner.

Identifiers

TryGetReferencedElement(documentName, identifier) builds the cache key from an identifier as it was written in the document:

Written Key looked up
_b (bare id) {documentName}#_b, the element's own document
#_b (same-document fragment) {documentName}#_b
other.xmi#_b other.xmi#_b
http://…/UML.xmi#Class as written
Co.xmi#xpointer((//*[@xmi:uuid='v'])[1]) as written; the cache resolves the XPointer uuid form

The document part of an href is used as written; the ExternalReferenceResolver registers the external document under exactly that name, so the two sides meet in the cache regardless of where the file was found on disk.

Resolution failures

ResolutionFailures (IReadOnlyList<XmiReferenceResolutionFailure>) lists every reference that the most recent Synchronize() could not resolve. Each failure records the declaring element (DocumentName, ElementXmiId, ElementXmiType), the PropertyName, the Identifier and a Kind:

Kind Meaning
NotFound no element with that identifier is in the cache; most often a document that was not available, such as the UML metamodel referenced from a profile
UnexpectedType the element exists but is not an instance of the property's type
AlreadyOwned a composite proxy refers to an element that already has another owner
MultiplicityExceeded a composite proxy would exceed the upper bound of the composite property

ToString() renders a failure as doc1.xml#idO1 (uml:Operation).ownedRule -> doc2.xml#idC4 [NotFound].

By default failures are logged as warnings and reading continues, since many real-world models, including the OMG normative files, reference documents that are not shipped with them. With IXmiReaderSettings.ThrowOnUnresolvedReferences = true, the XmiReader throws a single UnresolvedReferencesException listing them at the end of the read.

What the Assembler cannot resolve is not lost: the raw href elements stay in UnresolvedReferences on the declaring element and are written back verbatim by the XMI writers.

Reflection and the [Property] attribute

The Assembler finds target properties through the [Property] decorator that the generator puts on every property of the generated classes (see uml4net project, Decoration Attributes), matching on the property name and, for single-valued references, on the type of the referenced element. Only the public properties of the class are considered; a redefined member, which the generated class implements explicitly and forwards to the redefining property, is reached through the redefining property's own name.

A reference whose property cannot be found on the element's class is a programming error (a reader and a class that disagree), not a model error: it throws (InvalidOperationException / KeyNotFoundException) rather than being recorded as a failure.

When it runs

The XmiReader calls Synchronize() exactly once per read, after the root document and every external document reachable from it have been read. Synchronize() clears ResolutionFailures and processes every element in the cache.

The identifier dictionaries are not emptied by assembly: SingleValueReferencePropertyIdentifiers and MultiValueReferencePropertyIdentifiers keep their entries (they document what the element referred to), while resolved composite proxies are removed from CompositeReferencePropertyIdentifiers and resolved hrefs from UnresolvedReferences. Because multi-valued references are resolved by adding to the target list, a second Synchronize() over the same cache would add them again; the Assembler is a one-shot step at the end of a read, not something to call repeatedly.

See also

  • XmiElement — the identifier dictionaries the Assembler consumes
  • XmiElementCache — where identifiers are looked up
  • ContainerList — how inserting a resolved composite proxy sets ownership
  • uml4net.xmi project — external references, the ExternalReferenceResolver and reader settings

Clone this wiki locally