Generate typed Java ViewModels and discover declarative registry defaults

This commit is contained in:
2026-11-13 20:06:26 +01:00
parent adf432b98b
commit 8be0aec287
36 changed files with 1123 additions and 17 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ Build and Refresh already performs `clean package` in an isolated reactor copy.
## Boundaries and validation
This is build-time Model metadata for existing discovery and binding authoring. It does not generate Java ViewModel subclasses, a ViewModel registry, editable FXML forms, labels/control defaults, arbitrary BeanInfo metadata, Java source origins or project-wide Model selection UI. Those retain their later roadmap gates. The processor is a Java 21 build artifact depending only on the contracts; it is not packaged in either host or required on the application runtime path. Wire DTOs and protocol versions do not change.
This is build-time Model metadata for existing discovery and binding authoring. The subsequent [ViewModel generation slice](generated-viewmodels.md) now generates Java subclasses and registry defaults. Editable FXML forms, labels/control defaults, arbitrary BeanInfo metadata, Java source origins and a project-wide Model selection UI remain later work. Those retain their later roadmap gates. The processor is a Java 21 build artifact depending only on the contracts; it is not packaged in either host or required on the application runtime path. Wire DTOs and protocol versions do not change.
Automated tests compile real source with the processor, verify deterministic inherited/collection signatures and non-execution, clean-build removal, missing selections, schema bounds, generated/fallback equivalence, stale signatures and duplicate resources. Real classpath and module-path fixture processes require generated Person and nested Address metadata and retain their non-initialization assertions. The live shared-host scenario requires generated metadata provenance before handing discovery to the binding inspector, then runs existing source-preserving binding authoring and Undo/Redo tests in standalone and registered IntelliJ. No manual testing is requested; earlier skipped manual checks remain skipped.
+60
View File
@@ -0,0 +1,60 @@
# Generated Java ViewModels and registry defaults
The existing Model processor now optionally generates Java subclasses from the same `ModelMetadata` descriptions used by discovery. Generated accessors use NeitzelLib's stored Attribute properties; they do not create another property map or identity registry. Both hosts discover the configured default ViewModel from the compiled registry without executing providers, and Build and Refresh runs the configured generator in the application build.
## Configure the application build
Keep Model selections, generated package and hand-written defaults in the application module's checked-in compiler configuration, alongside the [metadata processor path](generated-model-metadata.md):
```xml
<compilerArgs>
<arg>-Adesigner.models=com.example.Person,com.example.Employee</arg>
<arg>-Adesigner.viewModels=true</arg>
<arg>-Adesigner.viewModelModels=com.example.Person,com.example.Employee</arg>
<arg>-Adesigner.generatedPackage=com.example.generated</arg>
<arg>-Adesigner.viewModelDefaults=com.example.Person=com.example.CustomPersonViewModel</arg>
</compilerArgs>
```
`viewModelModels` selects the generated subset of metadata Models; unknown selections fail compilation, and nested canonical class names are normalized; when omitted, it uses `designer.models`. `viewModelDefaults` is optional; a Model otherwise defaults to its generated class. Use a distinct generated package per participating module. The module must depend on the qualified NeitzelLib FX runtime. The processor remains a build dependency; runtime applications do not depend on designer hosts or target tooling.
Output goes to Maven's `target/generated-sources/annotations`, with names such as `PersonViewModel`. Hand-written subclasses remain ordinary source files and can extend the generated superclass in a clean compilation. Generated classes have a concrete Model constructor, typed `nameProperty()`, `getName()` and `setName(...)` methods. String/numeric/boolean properties use the runtime's specialized JavaFX classes. Complex Attributes expose typed child ViewModels, while known collections expose observable lists of Model elements. Existing runtime null, collection-setter and write-back semantics remain in force.
Generation includes available source ancestors and nested Models, visits cycles once, and preserves safe generated superclass APIs. Incompatible writable narrowing, name collisions, reserved runtime APIs, unsupported generic Model declarations and custom BeanInfo mappings are diagnosed. A nested complex type without an available generated mapping is rejected rather than exposed with an incorrect Model-valued accessor. Cross-module nested-generation mapping and broader generic/BeanInfo configurations remain outside this qualified slice.
Lombok accessor generation is tested with either processor ordering. A compiler source round allows accessors to complete; compiler source-tree ownership prevents test/dependent compilations from duplicating main-source registries. Clean builds remove obsolete generated sources, classes and resources. Removing an Attribute makes custom references fail compilation; the generator does not rewrite custom source. Build and Refresh already uses clean isolated builds and immutable snapshots. External incremental builds must clean after selection changes/removals.
## Runtime registry and selection
Each participating module generates `GeneratedViewModelRegistry`, a marker service with repeatable `ViewModelRegistration` annotations naming the Model, generated base and selected default. A generated compile-time assignment checks that a custom default extends its generated base. Classpath builds package the service descriptor automatically.
Named modules must declare the generated provider and make generated constructors accessible, for example:
```java
provides de.neitzel.fx.view.ViewModelRegistry
with com.example.generated.GeneratedViewModelRegistry;
exports com.example.generated;
opens com.example.generated to fx;
```
Existing Model and Controller access requirements still apply. Both classpath and named-module fixtures are qualified against the pinned runtime. The library inspects `ServiceLoader.Provider.type()` and annotations without constructing registry objects. Conflicting defaults identify both contributing registry classes. Default lookup chooses the most-specific configured Model ancestor; unconfigured subclasses can use their ancestor's default.
A compatible explicit `@ViewModelClass` takes precedence before first creation and must extend the applicable generated base. An existing compatible subclass satisfies later requests for its generated superclass; incompatible requests fail. Nested and direct loading share the same Model-identity mapping, including cycles and null children. Failed subclass construction withdraws its partial identity entry. Messages and ordinary binding behavior remain owned by the existing runtime.
The library extension is [NeitzelLib main `90e08b7`](https://gitea.neitzel.de/konrad/NeitzelLib/src/commit/90e08b7/fx/docs/generated-viewmodels.md). The [runtime manifest](runtime/neitzellib-baseline.json) pins the tested local artifact bytes. No released runtime version or remote artifact deployment is claimed.
## Host integration and boundaries
Standalone and IntelliJ use the same registry-aware target discovery. The browser shows the selected generated/custom class, uses the shared Model Attributes for authoring and retains the existing source-preserving edit/history path. Neither host loads application classes. The current UI invokes generation through **Build and Refresh** and checked-in Maven settings; a dedicated selection/settings editor and reviewed FXML form generation remain later work.
Automated acceptance covers clean consumer compilation, custom subclasses, both Lombok orders, removed Attributes, narrowing/reserved-name/default/BeanInfo errors, runtime stored identity/write-back, typed nested access, cycles/nulls, inheritance, collection element identity, Messages, compatible explicit/default selection, conflicting services and constructor failure cleanup. Real Maven classpath/module-path applications compile and load the generated classes independently of a designer. The shared live-host test discovers the generated custom default without changing FXML; existing authoring, Undo/Redo, diagnostics and lifecycle scenarios remain required in both hosts. No manual testing is requested.
## Qualification record (2026-11-13)
The complete bootstrap passed module builds, target linkage, Java bytecode baselines, plugin packaging, documentation checks, both standalone launch modes, native shared-Kit workflows, 48 worker lifecycle cycles and abrupt owner death. The final processor-selection check then passed all ten processor tests (including six new generation tests) and was installed for a fresh fixture run: 38 target processes passed 548 checks across classpath and module-path applications. Combined current reports contain 151 designer unit/adapter tests and zero failures, errors or skips. The independently built NeitzelLib runtime passed 116 tests with zero failures, errors or skips; its Surefire XML was checked explicitly.
Evidence logs are `/tmp/generated-vm-bootstrap.log`, `/tmp/generated-vm-processor-final.log`, `/tmp/generated-vm-fixtures-final.log`, `/tmp/generated-vm-registered-ide.log`, `/tmp/generated-vm-ide-modulepath-retry.log`, `/tmp/generated-vm-ide-modulepath-final.log` and `/tmp/viewmodel-runtime-qualified.log`; host evidence is retained under `target/connected-workflow` and `target/ide-workflow`. These local outputs are disposable, not release artifacts. No manual testing was performed, and previously skipped human scenarios remain skipped.
The first registered IntelliJ run passed the classpath workflow but stopped in module-path mode at the previously recorded immediate exact-save assertion, before generation checks. An unchanged module-path retry timed out at initial canvas rendering (`WorkflowProbe` line 438), also before generation checks. No assertion was weakened. Existing startup/persistence/preview-gate reliability work remains open; this slice does not claim to fix those intermittent failures.
The final unchanged module-path retry hit the same initial-render timeout. Consequently, **registered IntelliJ module-path whole-host qualification remains open** for this slice. Registered IntelliJ classpath passed generated-registry discovery, full shared editing/history/diagnostic checks, exclusive JavaFX 26 native verification and worker shutdown. Standalone and real target-process module-path checks passed, including generated classes and registry behavior; these do not substitute for the missing registered IDE pass. Follow up on initial rendering and save synchronization before declaring that host gate complete.
+6
View File
@@ -149,3 +149,9 @@ The [shared highlighting/fixes slice](source-highlighting-fixes.md) adds current
## Generated Model metadata
The [generated Model metadata slice](generated-model-metadata.md) adds an opt-in compiler processor, versioned deterministic resources, generated-first target discovery with signature integrity checks, and shared provenance in both hosts. Existing binding authoring consumes the resulting Attributes without host-specific logic or new undo paths. Missing metadata retains compiled fallback; invalid present metadata is diagnosed. Java ViewModel/registry and FXML form generation, custom BeanInfo semantics and broader source origins remain later work.
## Generated Java ViewModels and registry defaults
The [ViewModel generation slice](generated-viewmodels.md) extends the metadata processor with typed replaceable Java sources, checked-in Maven settings and declarative registry services. NeitzelLib supplies stored-property lookup and compatible shared identity/default selection. Both hosts discover generated/custom defaults through target workers and keep existing authoring/history behavior. Clean consumer builds, Lombok, generated inheritance/nesting and rejection cases have automated coverage. Dedicated configuration selection UI, broader cross-module/generic mappings and reviewed FXML form generation remain later work.
Qualification: automated standalone and registered IntelliJ classpath workflows pass. Real module-path consumer/runtime tests pass, but registered IntelliJ module-path remains open after the existing save assertion and initial-render timeouts; see the qualification record above. No manual testing was requested or substituted.
+7 -6
View File
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"status": "verified-local-development-snapshot",
"sourceRepository": "ssh://git@gitea.neitzel.de:2222/konrad/NeitzelLib.git",
"sourceRevision": "2d83fb467cc29b6e7c5aa92c86fc3fb49a504dc0",
"sourceRevision": "90e08b7835501f6810f12b845d7a9dacd7749425",
"minimumReleasedVersion": null,
"requiredCapability": "component-fxml-attributes-after-initialization",
"javaVersion": "21",
@@ -21,7 +21,7 @@
"skipped": 0
},
"fx": {
"tests": 101,
"tests": 105,
"failures": 0,
"errors": 0,
"skipped": 0
@@ -51,7 +51,7 @@
"version": "1.0-SNAPSHOT",
"extension": "jar",
"repositoryPath": "de/neitzel/lib/core/1.0-SNAPSHOT/core-1.0-SNAPSHOT.jar",
"sha256": "6610e43792818fd5d4d3545d487b28d0cd3faa9b849046a47a4db02c984a144a"
"sha256": "2fc2b717f48628f3fd24437fe480f5acfeb55cd2c7d4a65fdd1df0275cd28810"
},
{
"groupId": "de.neitzel.lib",
@@ -67,7 +67,7 @@
"version": "1.0-SNAPSHOT",
"extension": "jar",
"repositoryPath": "de/neitzel/lib/injection/1.0-SNAPSHOT/injection-1.0-SNAPSHOT.jar",
"sha256": "a582aa71f034370865d486706f55a1fbcc40446c47bf402c28049b205f7c9e25"
"sha256": "45ef4ce24a23164faa42bd3568015be30808f87328fa4ef2ec8cc57ba1eb027d"
},
{
"groupId": "de.neitzel.lib",
@@ -83,7 +83,8 @@
"version": "1.0-SNAPSHOT",
"extension": "jar",
"repositoryPath": "de/neitzel/lib/fx/1.0-SNAPSHOT/fx-1.0-SNAPSHOT.jar",
"sha256": "a668ac49f5a44d63e7e115fd93456b581ab05725870affcaf2dede6ec5dbe431"
"sha256": "cc0cbde64cc09e1b0711727752e7965442044330d976eae054e14237456a8f40"
}
]
],
"generatedViewModelSupport": "stored-property-access-and-declarative-service-registry"
}
+6
View File
@@ -33,3 +33,9 @@ Use a clean checkout at the manifest's source revision; the canonical library ch
The command-line failure-ignore property does not override the existing hardcoded POM setting. Inspect every `TEST-*.xml` report under `core`, `injection` and `fx`: require nonempty reports and zero failures/errors/skips before accepting the install. Re-run the direct adapter-free scenario before changing the recorded capability baseline. Record source revision and installed artifact hashes together after acceptance; do not silently reuse the old hashes for a new build.
The regression source covers custom/inherited properties, numeric/boolean/enum conversion, XML text escaping, attached layout constraints, post-initialization setters, shared Model identity/write-back and invalid values. The original canonical library branch and its working-tree changes were left untouched; only the isolated eight-file Component change was integrated into main.
## Generated ViewModel runtime extension — 2026-11-13
The active development snapshot now includes NeitzelLib `90e08b7`: [stored-property access and generated defaults](https://gitea.neitzel.de/konrad/NeitzelLib/src/commit/90e08b7/fx/docs/generated-viewmodels.md). The earlier Component baseline above remains historical qualification. The new isolated clean build passed core 1, injection 10 and fx 105 tests with no failures/errors/skips. Every installed class was compared with the tested class output before updating the manifest hashes. The canonical library checkout and its unrelated changes remain untouched. No released minimum or remote artifact publication is claimed.
Designer fixtures additionally verify generated runtime behavior in real classpath and module-path applications. The library build evidence is `/tmp/viewmodel-runtime-qualified.log`; generated fixture evidence is `/tmp/generated-vm-fixtures-final.log`. The prior 38 adapter-free checks remain historical; current whole-host qualification is recorded with the generated ViewModel slice.