-
Notifications
You must be signed in to change notification settings - Fork 0
Codegen Design
Reference implementations studied: uml4net.CodeGenerator, ECoreNetto + ECoreNetto.HandleBars
This is the Phase 2 design deliverable: how Auriga.CodeGenerator turns the vendored Capella .ecore files (resources/ecore, 21 files / 24 EPackages / ~430 EClasses / 35 EEnums / 0 custom EDataTypes — see the metamodel inventory) into the committed C# object model in the Auriga project. It is written to be implemented as-is; every non-obvious choice is called out as a Decision with its rationale, and points that must be confirmed against the real metamodel during implementation are marked Verify during implementation.
uml4net.CodeGenerator loads the OMG UML.xmi metamodel through uml4net.xmi into a typed object graph, then runs one Handlebars template per model element to emit committed C# (an AutoGen* folder tree of partial types), reformatting each file with Roslyn before writing, and gating regeneration with golden-file tests. Auriga mirrors this exactly, with three substitutions: the input graph is the ECoreNetto EPackage → EClass → EStructuralFeature graph (not UML); the reusable helper layer is ECoreNetto.Extensions + ECoreNetto.HandleBars (not uml4net.Extensions/uml4net.HandleBars); and Auriga supplies the one thing ECoreNetto has no opinion on — an Ecore-primitive → C# type mapping. Deserialization dispatch follows uml4net's single XmiElementReaderFacade (not a per-package factory), with the lookup key adapted to Capella's (nsURI, localName).
The generator is a thin orchestrator, not a transformer chain. Auriga.CodeGenerator (already scaffolded, targets net10.0, references ECoreNetto + ECoreNetto.HandleBars 9.0.0) contains:
Auriga.CodeGenerator/
Generators/
CorePocoGenerator.cs // THE code generator: orchestrates enums -> interfaces -> classes
Helpers/
CSharpType.cs // Ecore->C# type mapping + nullability + collection rendering
CSharpNaming.cs // casing, reserved-word @-escaping, namespace + interface/enum names
Models/ // the HandleBars template contexts (one file per model type)
EnumModel.cs, InterfaceModel.cs, ClassModel.cs, MemberModel.cs, LiteralModel.cs
Templates/
core-enumeration-template.hbs
core-poco-interface-template.hbs
core-poco-class-template.hbs
The class that performs code generation is Generators/CorePocoGenerator (the uml4net/SysML2.NET CorePocoGenerator/UmlCorePocoGenerator convention); its tests are Auriga.CodeGenerator.Tests/Generators/CorePocoGeneratorTestFixture (structure, counts, model-inspector coverage) and CorePocoGeneratorExpectedTestFixture (golden files).
(The XMI reader/writer generators — including the single XmiElementReaderFacade, §3 — are a related set of generators that emit into Auriga.Xmi; they belong to the reader work but the dispatch decision is recorded here.)
Decision — reuse ECoreNetto's helper projects rather than create Auriga.HandleBars/Auriga.Extensions. uml4net needed its own uml4net.HandleBars/uml4net.Extensions because nothing upstream understood UML. ECoreNetto already ships ECoreNetto.Extensions (multiplicity, containment, documentation, casing, package flattening, specialization queries) and ECoreNetto.HandleBars (the block helpers that call them). Auriga registers those, then adds only the C#-specific helpers above. This keeps Auriga.CodeGenerator small and avoids re-implementing the graph-query layer.
Generation flow (in CorePocoGenerator, driven from Auriga.CodeGenerator.Tests — see §10, following uml4net which has no CLI and drives generation from NUnit):
-
Load the metamodel: one
ResourceSet, load all 21.ecorefiles, collect every rootEPackage, flatten withPackageExtensions.QueryPackages()to the 24 packages. (This is exactly the load the ECoreNetto validation tests already exercise; the harness code is a working template.) -
Bucket each package's
EClassifiersintoEEnum,EClass(abstract + concrete), followingHandleBarsReportGenerator.CreateHandlebarsPayload. There are no customEDataTypes to handle (inventory §2), so the datatype bucket is asserted empty — Verify during implementation. -
Emit, in dependency-safe order (enums, then interfaces, then classes), one file per artifact, each passed through
CodeCleanupbefore write.
Decision — interface always, implementation class for concrete only; no per-class or per-package factory. For a metaclass PhysicalFunction in package pa:
| Artifact | Emitted when | File → committed location | Namespace |
|---|---|---|---|
interface IPhysicalFunction |
always (abstract + concrete) | AutoGenInterfaces/Model/Pa/IPhysicalFunction.cs |
Auriga.Model.Pa |
class PhysicalFunction |
only when not Abstract
|
AutoGenClasses/Model/Pa/PhysicalFunction.cs |
Auriga.Model.Pa |
enum <Name> |
per EEnum
|
AutoGenEnumeration/Model/Pa/<Name>.cs |
Auriga.Model.Pa |
(The Sirius/GMF metamodel is generated by the same pattern into the Diagram sub-folder and the Auriga.Diagram.* namespaces of the same project.)
The interface carries the contract and the type lattice; the class carries state. Object construction is plain new PhysicalFunction() — the concrete classes are directly instantiable. Type-name → instance dispatch for deserialization is handled by a single generated facade (below), not by factories.
Decision — flatten the inheritance lattice onto concrete classes; keep true multiple inheritance only on interfaces. The Capella metamodel uses pervasive multiple inheritance (e.g. fa::AbstractFunction has 6+ supertypes; oa::Entity is a cs::Component; see the Arcadia notes §6). C# has single class inheritance, so — exactly as uml4net does — :
-
interface IPhysicalFunction : IAbstractFunction, IExtensibleElement, …lists all direct supertype interfaces (EClass.ESuperTypes), preserving the full lattice. -
class PhysicalFunction : AurigaElement, IPhysicalFunctionderives from one hand-written base (AurigaElement, §9) and re-declares every inherited feature, obtained fromEClass.AllEStructuralFeatures(the flattened own+supertypes set).AllEStructuralFeaturescan contain duplicates across the hierarchy, so de-duplicate by name before emitting (ECoreNetto does not dedupe). Order by name for deterministic output.
This makes each concrete class self-contained (no C# base-class chain to walk) and sidesteps the diamond problem entirely, at the cost of member repetition — the same trade uml4net accepts.
Decision — a single generated XmiElementReaderFacade dispatches type → instance, following uml4net; no factories. The original design asked for a per-package PaFactory (the EMF Java convention), but on review we take the uml4net route instead: one generated dispatcher for the whole model, because a single lookup does everything a per-package factory would, without the ~20 extra factory classes and a registry to wire them together. uml4net's XmiElementReaderFacade (uml4net.xmi/AutoGenXmiReaders/XmiElementReaderFacade.cs) holds a Dictionary<string, Func<…>> keyed by the xmi:type string; each value is a lambda that builds the right per-class reader and returns the constructed element; QueryXmiElement reads the type attribute and dispatches, throwing on an unknown type. The per-class readers construct with plain new Foo().
Auriga adopts the same single-facade shape with one Capella-specific change to the key. uml4net can key on the raw string "uml:Class" because UML XMI always uses the fixed uml: prefix. Capella's prefix is arbitrary per document (bound to an nsURI via xmlns:), and the same local name (e.g. Component) exists in several packages, so the facade must key on the resolved type — the pair (nsURI, localName) — after the reader resolves the element's prefix to its nsURI. Same dictionary-dispatch design, resolved key:
- The facade is keyed by
(nsURI, localName)→ a construction/reader delegate.nsURIcomes from resolving the element's XML namespace;localNameis the classifier name. Both are already in hand at the point the reader hits an element. - An unknown
(nsURI, localName)throws a clear "no reader for type" error — and, because abstract classes contribute no entry, anxsi:typenaming an abstract class fails loudly rather than silently. - The facade and the per-class readers are generated by the XMI reader generator (mirroring uml4net's
XmiReaderGenerator) intoAuriga.Xmi; this is coupled to the reader work, but the dispatch decision belongs to the generation design and is recorded here.
Object construction stays plain new Foo(); the public "create a model in code" story is simply the instantiable concrete classes, exactly as in uml4net.
Every generated type is partial (see §9/§10). File/type naming: CapitalizeFirstLetter(EClass.Name) for classes/enums, "I" + CapitalizeFirstLetter(Name) for interfaces (ECoreNetto.Extensions.StringExtensions.CapitalizeFirstLetter is reused; it throws on empty names — every classifier in the Capella metamodel is named, but assert this).
Decision — <root>.<PascalCasePackagePath>, derived purely from the Ecore object graph. The root namespace is the per-metamodel knob held by NamingContext — Auriga.Model for the Capella metamodel and Auriga.Diagram for the Sirius/GMF metamodel (both compiled into the single Auriga assembly; a follow-up merged the previously separate assemblies into one library with namespace separation). For each package, build the dotted path from the package-name chain (EClassifier.EPackage walked up via ESuperPackage, or EPackageTree), PascalCasing each segment:
| EPackage (ns prefix) | C# namespace |
|---|---|
modellingcore |
Auriga.Model.Modellingcore |
capellacore |
Auriga.Model.Capellacore |
pa |
Auriga.Model.Pa |
pa.deployment (sub-package) |
Auriga.Model.Pa.Deployment |
information |
Auriga.Model.Information |
information.datavalue (sub-package) |
Auriga.Model.Information.Datavalue |
Requirements |
Auriga.Model.Requirements |
CapellaRequirements |
Auriga.Model.CapellaRequirements |
viewpoint (Sirius) |
Auriga.Diagram.Viewpoint |
diagram (Sirius) |
Auriga.Diagram.Diagram |
Root package names are unique within each metamodel (inventory §2), so there are no collisions; sub-packages nest under their parent, matching the four nested EPackages (pa.deployment, information.communication/datatype/datavalue). Identically-named types across the two metamodels (e.g. Folder) cannot collide because each metamodel has its own namespace root. This is self-contained (needs only the .ecore graph, not the .genmodel files) and mirrors uml4net's uml4net.<Package.Name> convention.
Rejected alternative: mirroring the EMF .genmodel basePackage values (Auriga.Common.Data.Activity, Auriga.Core.Data.Pa.Deployment, as the inventory §1 floated). It reads more like the Java packaging, but the basePackage lives in the .genmodel files, not the .ecore files the generator consumes — encoding it would mean a hand-maintained package→basePackage config table. Not worth the coupling; the flat form above is unambiguous. Revisit only if a downstream consumer needs the common/core split.
The rules below drive both the interface property signature and the class property. They are computed from ECoreNetto members; ETypedElement.Many and .Required are unreliable (EMF does not serialize the derived many=/required= attributes, so they read false regardless) — always derive from bounds, as ECoreNetto.Extensions itself does.
| Feature shape | Detection (ECoreNetto) | C# rendering |
|---|---|---|
| collection |
UpperBound == -1 || UpperBound > 1 (StructuralFeatureExtensions.QueryIsEnumerable) |
see below |
| required scalar |
LowerBound >= 1 && not collection |
T (non-null) |
| optional scalar |
LowerBound == 0 && not collection (QueryIsNullable) |
value type → T?; string/reference → T
|
| containment reference | EReference.IsContainment |
scalar → IFoo; multi → IContainerList<IFoo> (owning) |
| cross-reference |
EReference && !IsContainment
|
scalar → IFoo; multi → List<IFoo>
|
| attribute, primitive |
EAttribute, EType is a built-in EDataType
|
mapped C# type (§6) |
| attribute, enum |
EAttribute, EType is EEnum (QueryIsEnum) |
the generated enum type |
Collections are never null (initialized: List<T> → = new(); IContainerList<T> → owner-aware backing, §9). Scalars keep Ordered/Unique only as metadata; C# does not model them structurally.
Containment vs cross-reference matters twice: it selects the collection type here, and it tells the reader/writer whether the target is written inline (containment) or as an href (cross-reference). The generator records containment on the property (via the collection type and, if useful, an attribute) so the XMI layer can branch without re-querying the metamodel.
Derived / volatile / transient features — the crux for Capella. Per the Arcadia notes, essentially all traceability/convenience features (realizedFunctions, allocatedFunctions, system, the containedX aliases, …) are derived volatile transient and are not present in .melodymodeller XMI. They are also exactly the cyclic references that make per-file generation impossible (inventory §3).
Decision — emit derived features as get-only computed properties delegating to a hand-written partial method, and skip them in serialization. Detected via EStructuralFeature.Derived (also treat Volatile/Transient the same for storage purposes):
// in interface: get-only
IEnumerable<IAbstractFunction> RealizedFunctions { get; }
// in class: expression-bodied, delegates to a hand-written partial
public IEnumerable<IAbstractFunction> RealizedFunctions => this.QueryRealizedFunctions();QueryRealizedFunctions() is authored by hand in the Extend/ partial (§9/§10). The XMI reader and writer skip any feature where Derived || Transient || Volatile, which both matches the on-disk reality and breaks the reference cycles. Non-derived, non-changeable (Changeable == false) features become { get; } without a computed body only if they still carry stored state — Verify during implementation whether Capella has any read-only-but-stored features (expected: none; derived covers them).
Redefinition / subsetting. Ecore expresses these far more weakly than UML (no first-class redefines/subsets on EReference; at most EAnnotations). uml4net's elaborate [RedefinedProperty]/[SubsettedProperty]/explicit-interface-throw machinery is not ported in v1. If a Capella feature name collides with an inherited feature of the same name during the AllEStructuralFeatures dedupe, keep the most-derived declaration and drop the ancestor (record the drop in a generation log). Verify during implementation that dedupe-by-name is sufficient across the real hierarchy (e.g. the ownedX/containedX alias pairs).
ECoreNetto ships no primitive-type table — feature.EType.Name yields the Ecore datatype name string ("EString", "EInt", "EBoolean", …) and that is all. This table is new code Auriga owns (CSharpTypeHelper), applied when an attribute's EType is a built-in EDataType (not an EEnum, not an EClass):
| Ecore datatype | C# type | Ecore datatype | C# type | |
|---|---|---|---|---|
EString |
string |
EBoolean / EBooleanObject
|
bool |
|
EInt / EIntegerObject
|
int |
ELong / ELongObject
|
long |
|
EShort / EShortObject
|
short |
EByte / EByteObject
|
sbyte |
|
EFloat / EFloatObject
|
float |
EDouble / EDoubleObject
|
double |
|
EBigDecimal |
decimal |
EBigInteger |
System.Numerics.BigInteger |
|
EChar / ECharacterObject
|
char |
EByteArray |
byte[] |
|
EDate |
System.DateTime |
EJavaObject |
object |
|
EBooleanObject etc. (the *Object boxed forms) |
nullable applies via §5 bounds, not the table | EJavaClass |
System.Type |
Optionality is applied on top by §5 (a LowerBound==0 EInt becomes int?), so the table maps to the non-null base type. Capella uses only a small subset — the inventory found only Ecore primitives and enums, in practice EString, EBoolean, EInt dominate — but the full table is cheap and future-proofs against customer DSLs. The mapping is a Dictionary seeded with the above and overridable (mirroring uml4net's AddOrOverwriteCSharpTypeMappings) so a later metamodel can extend it. An unmapped datatype name is a hard generation error (fail loud, do not pass the raw Ecore name through).
Capella carries rich documentation in EAnnotations (the inventory/Arcadia work found http://www.polarsys.org/kitalpha/ecore/documentation as a top annotation source, plus HTML-laden descriptions). Reuse ECoreNetto.Extensions.ModelElementExtensions.QueryDocumentation(), which pulls the documentation detail key out of an element's EAnnotations, strips <p>/<code>/<em>/<tt> via HtmlAgilityPack, and rewraps to ~100-char lines — precisely what a /// <summary> needs. The template emits:
/// <summary>
/// <one line per wrapped documentation line>
/// </summary>on every interface, class, enum, enum literal, and property, via a documentation block helper (reuse ECoreNetto.HandleBars' RawDocumentation/documentation helper, adapted to emit /// lines instead of HTML). Verify during implementation that QueryDocumentation actually resolves Capella's kitalpha documentation annotations (ECoreNetto reads the documentation key regardless of annotation source, so it should — but confirm against a real class like pa::PhysicalComponent, and fall back to a "No documentation" summary when empty rather than emitting an empty <summary>).
-
Casing: interface
I+CapitalizeFirstLetter(Name); class/enum/fileCapitalizeFirstLetter(Name); enum literals capitalized. ReuseECoreNetto.Extensions.StringExtensions. -
Enum literal round-trip:
EEnumLiteral.Literal(serialized form) can differ fromName. Generate the C# member from the capitalizedName, and preserve the originalLiteral(e.g. an[System.Runtime.Serialization.EnumMember(Value = "…")]attribute or a generated name↔literal lookup) so the XMI layer round-trips values that don't match the C# identifier. Verify during implementation which Capella enums have divergent literals. -
Reserved words: replace uml4net's ad-hoc six-word switch with a complete C# keyword set; any member (or type) name colliding with a keyword is
@-escaped. Capella will hit this (e.g. a feature namedabstract/object/eventis plausible) — Verify during implementation by scanning feature names against the keyword list. -
Member name == enclosing type name (illegal in C#): if the offending feature is a collection, pluralize (
+ "s"); if scalar, suffix a disambiguator (e.g.+ "Value") rather than uml4net'sthrow. Log every rename. -
Nullability: the project is nullable-enabled (solution-wide
<Nullable>enable</Nullable>). Optional value-typed scalars →T?; optional references/strings stayT(reference-nullable is not annotated in v1 to avoid churn); collections are non-null and initialized. Decision: keep nullable enabled (unlike uml4net which disables it) and render value-type optionality explicitly, because the generated model is new code and should be null-correct from the start.
Generation is only useful on top of a small hand-authored runtime in the Auriga project (analogous to uml4net's XmiElement and EMF's EObjectImpl). These are written once, by hand, not generated:
-
AurigaElement(base of every generated concrete class): identity (Id/xmi:id), a container back-pointer, and the plumbing the object graph needs. Everyclass Foo : AurigaElement, IFoo. -
IContainerList<T>/ContainerList<T>: an owner-aware collection for containment references that sets the child's container on add/remove (the equivalent of EMF's containmentEList). Non-containment multi-valued features use plainList<T>. -
IAurigaElement(or reuse the generatedmodellingcore::ModelElementinterface as the common root) — decide whether the hand-written base implements a marker interface or the generatedIModelElement. Decision:AurigaElementimplements a minimal hand-writtenIAurigaElementmarker (Id + container); the generatedmodellingcore::ModelElementinterface extends it. This keeps the runtime independent of generated code while giving every element the identity contract. -
Hand-written
Extend/partials: the bodies of derived features (QueryRealizedFunctions()etc.). These live beside — but separate from — the generatedAutoGen*files, in the samepartialtype.
There is no factory runtime — object construction is new Foo(), and deserialization dispatch is the generated XmiElementReaderFacade (§3), which lives in Auriga.Xmi, not the object-model runtime.
Decision — commit the generated code into the Auriga project and gate it with golden-file tests, exactly as uml4net does.
-
Output location (committed to git, in the
Aurigalibrary). Each classifier is written to a metamodel sub-folder (Modelfor Capella,Diagramfor Sirius — the unit the generator clears on a regeneration, so one metamodel's run never deletes the other's output) and then a sub-folder named after its Ecore package (PascalCased), so classes that share a simple name in different packages (FolderincapellamodellerandRequirements;AbstractTypeinmodellingcoreandRequirements) do not collide:The generator emits the whole v1 metamodel by default (430 interfaces, 275 concrete classes, 35 enums across 21 packages that contain concrete types);Auriga/AutoGenInterfaces/<Metamodel>/<Package>/I*.cs e.g. AutoGenInterfaces/Model/Pa/IPhysicalComponent.cs Auriga/AutoGenClasses/<Metamodel>/<Package>/<concrete>.cs e.g. AutoGenClasses/Diagram/Viewpoint/DAnalysis.cs Auriga/AutoGenEnumeration/<Metamodel>/<Package>/<enum>.cs e.g. AutoGenEnumeration/Model/Pa/PhysicalComponentNature.cs Auriga/Extend/ hand-written partials (derived bodies, behavior)Generate(params packageNames)still accepts a package subset for targeted runs.ecore::EObject(reached only as a feature type inre.ecore) is an Ecore built-in, not part of the model, and maps toobject. (The generatedXmiElementReaderFacadeand per-class readers/writers land inAuriga.Xmi, not here.) -
Every generated file starts with the Starion SPDX copyright header (emitted from the top of each
.hbstemplate,file="{{Name}}.cs"interpolated — matching CONTRIBUTING) and an auto-generated banner, and every generated type carries[GeneratedCode("Auriga.CodeGenerator", "<version>")]. Files are plain.cssegregated byAutoGen*folder (no.g.cs). -
All generated types are
partial. Structure + auto-properties + attributes are generated; derived-property bodies and any behavior live in hand-writtenpartials underExtend/. Regeneration never touchesExtend/. -
Determinism: order everything by name (
OrderBy(x => x.Name)), dedupe flattened features by name, and run every file through RoslynCodeCleanup(CSharpSyntaxTree.ParseText+Formatter.Formatover anAdhocWorkspace) so output is byte-stable and templates can be whitespace-sloppy. -
Regeneration is test-driven (no CLI, matching uml4net):
-
Golden-file verification tests in
Auriga.CodeGenerator.Testsgenerate into a throwaway temp dir and assert string-equality against the committedAutoGen*files. This is the CI gate: any drift between the templates and the committed output fails the build. (The vendored.ecorefiles are already copied to the test output by theAuriga.CodeGenerator.Tests.csprojData/ecoreitem.) - One
[Explicit]test (Regenerate_object_model) walks up to the solution root and writes theAutoGen*folders in place. A developer runs it after changing a template, reviews the diff, and commits. The normal test run never runs the explicit test. The regeneration command is:dotnet test Auriga.CodeGenerator.Tests --filter "FullyQualifiedName~Regenerate_object_model" -
No-drift CI guard: the
codegen-driftjob inCodeQuality.ymlruns exactly this explicit regeneration, thengit diffs theAutoGen*folders and fails the build if the freshly generated code differs from what is committed — a change to a template or the generator that is not accompanied by a regenerate cannot be merged. Where the golden-file tests check a representative subset byte-for-byte, this guard covers every generated file. Generated output is normalized to LF (CorePocoGenerator.Normalize, plus.gitattributes) so the guard is stable across Windows and Linux. -
Expected-results verification:
Verify_that_every_interesting_class_from_the_model_inspector_is_generatedruns ECoreNetto'sModelInspectorover the vendored metamodel and asserts every "interesting class" (the minimal set covering all type/multiplicity variations) has a generated interface. The full inspection report is committed atmodel-inspection.txtand regenerated with:dotnet test Auriga.CodeGenerator.Tests --filter "FullyQualifiedName~Regenerate_model_inspection_report"
-
Golden-file verification tests in
-
Input is
resources/ecore(the 21 vendored files), loaded exactly as the ECoreNetto validation tests do.
The same generator also emits the XMI readers consumed by Auriga.Xmi, following the uml4net pattern of a generated per-type reader on top of a hand-written core:
-
XmiReaderGenerator(inGenerators/, sharingMetamodelLoaderwith the model generator) emits, into theAuriga.Xmiproject (under theModelsub-folder /Auriga.Xmi.Model.*namespaces for Capella,Diagram/Auriga.Xmi.Diagram.*for Sirius):-
AutoGenXmiReaders/<Metamodel>/<Package>/<Type>Reader.cs— one reader per concrete class, driven from the ECoreNetto POCOs throughXmiReaderHelper. Each feature is classified exactly as uml4net'sPropertyHelperdoes — scalar / enum attribute, single- or multi-valued#idreference (a Capella attribute), or single/collection containment (a child element carryingxsi:type) — and the matching read code is emitted. -
AutoGenXmiReaders/<Metamodel>/XmiReaderFacade.cs— the registry mapping a package-qualified type key (package:TypeName) to its reader, resolving an element'sxsi:typevia the document's namespace bindings. -
AutoGenXmiReaders/<Metamodel>/AutoGenNamespaceRegistry.cs— the map from each package's XML namespace URI to its Ecore package name.
-
-
Hand-written core in
Auriga.Xmi(XmiReader,XmiElementCache,NamespaceResolver,ReferenceResolver,XmiElementReader<T>,XmiReaderBuilder) implements the two-pass load: instantiate + cache on the first pass, resolve the collected#idreferences on the second. The model base (AurigaElement) carries the deferred-reference dictionaries the readers populate. -
Regeneration (drift-guarded and golden-file tested like the model):
dotnet test Auriga.CodeGenerator.Tests --filter "FullyQualifiedName~Regenerate_xmi_readers" dotnet test Auriga.CodeGenerator.Tests --filter "FullyQualifiedName~Regenerate_expected_readers" -
Scope: single-file
.melodymodeller/.capelladocuments with intra-file#idreferences. Cross-file fragments (hrefinto.capellafragment) are a later concern.
- No custom
EDataTypes exist (assert the datatype bucket is empty). -
QueryDocumentationresolves Capella's kitalpha documentation annotations on a real class; empty-doc fallback wording. - Dedupe-by-name is sufficient for the flattened
AllEStructuralFeatures(theownedX/containedXalias pairs, redefinition-like collisions). - Which enums have
Literal != Name(round-trip mapping needed). - Feature/type names colliding with C# keywords or with their enclosing type (escaping/rename rules exercised).
- Whether any feature is
Changeable == falseyet stored (expected none — derived covers it). - The exact shape of
AurigaElement/IContainerList<T>(the runtime is hand-written and small, but its API is load-bearing for the generated code) and the(nsURI, localName)key theXmiElementReaderFacadedispatches on (load-bearing for the reader).
- Metamodel inventory — packages, counts, dependency cycles, the "generate from the whole resolved graph, never file-by-file" conclusion
- Arcadia notes — why derived features are transient/volatile and must be skipped in serialization; the multiple-inheritance and containment realities the generator must handle
- ECoreNetto validation — the load that the generator's front-end reuses
- Reference: uml4net.CodeGenerator (
Generators/,Templates/,Transformers/) and its committeduml4net/AutoGen*output; ECoreNetto.Reporting (HandleBarsReportGenerator,HtmlReportGenerator) as the closest working ECoreNetto+HandleBars emitter
Project background
Metamodel & design
- Metamodel Inventory
- Sirius Metamodel Inventory
- Arcadia Notes
- ECoreNetto Validation
- Sirius ECoreNetto Validation
Code generation
Reading & writing models
Diagrams
API