intent: the model stops carrying Java - a check's literal and condition become data - #7407
Merged
Merged
Conversation
…on become data (#7405) `checks[]` was the one place in the model that held Java SOURCE. `EdmIntentGenerator` compiled the comparison literal and the condition and wrote the result into the `.edm` - an artefact an author opens in the modeler - and on into the `.model` and `.model.gen`: "literal": "new java.math.BigDecimal(\"0\")" "guard": "!java.util.Objects.equals((hop0 == null ? null : hop0.Status), 1)" The `.model` is the shared, language-neutral description every template reads, Java and JavaScript alike, which is why `JavaLiterals` has a `JsonLiterals` twin and why a property default has always crossed this boundary the other way round: the model carries `dataDefaultValue`, `ModelParameterProcessor` derives `dataDefaultValueJavaLiteral`. `checks` was the only feature that skipped it - while emitting the neutral shape for its own UI half 250 lines away, and while the template already wrote the comparison's left-hand side in Java itself. The model now carries the same facts as data: "value": { "kind": "number", "text": "0" } "when": [ { "owner": "hop0", "property": "Status", "equal": false, "type": "integer", "value": "1", "numericKey": false } ] - `CheckSupport` keeps every refusal - the parser rejects an unrenderable literal while the author is generating - and returns the typed reading instead of Java. It renders the Java for exactly one caller that still needs it, the declarative glue lists, from the very same terms, so the two outputs cannot disagree (#7406 takes the glue). - `JavaLiterals` gains `compareLiteralExpression` and `conditionExpression`; `ModelParameterProcessor.splitChecks` calls them in the loop that already derives `messageJavaLiteral`, emitting `literalJavaExpression` / `guardJavaExpression`. - The DAO repository and the three REST controllers read the derived keys. - `ResolvePathSupport.Path` gains `owner` / `property` - `expression` taken apart, for a caller that must describe a read as data rather than emit it as code. The generated Java is byte-identical: `IntentEmissionCoverageIT`, which asserts on the generated controller text rather than on the model, passes unchanged, and the four `EdmIntentGeneratorTest` cases now pin the neutral model AND the Java rendered from it so the two halves cannot drift. Fixes #7405 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
checks[]was the one place in the model that held Java source.EdmIntentGeneratorcompiled the comparison literal and the condition and wrote the result into the.edm— an artefact an author opens in the Entity Data Modeler — and on into the.modeland.model.gen:The model now carries the same facts as data, and the Java is rendered a layer out, by
JavaLiterals:Why
The
.modelis the shared, language-neutral description every template reads, Java and JavaScript alike — which is whyJavaLiteralshas aJsonLiteralstwin, and why a property default has always crossed this boundary the other way round: the model carriesdataDefaultValue,ModelParameterProcessor.resolveDefaultValueLiteralsderivesdataDefaultValueJavaLiteral.checkswas the only feature that skipped that split, while:forbidWhenproduced"masterGuard": [{"property": "Status", "equal": false, "value": "1"}]next to a Java string for the identical condition;EntityController.java.template:963), importing only the right-hand side pre-baked, for no reason.What changed
CheckSupportkeeps every refusal — the parser rejects an unrenderable literal while the author is generating (IntentParser:4703) — and returns the typed reading instead of Java.condition(...)still renders Java for the one caller that needs it, the declarative glue lists (intent: notifications/integrations/outbound event.when never resolves a seeded status name and degrades an unparsable guard to true #7289), but now asjavaCondition(conditionTerms(...))— one reader, two outputs, so they cannot disagree. The.glueartefact itself is engine-intent: the .glue carries Java source too - expression keys need the same neutral-model split as checks[] (#7405 follow-up) #7406.JavaLiteralsgainscompareLiteralExpression(reading)andconditionExpression(terms).ModelParameterProcessor.splitCheckscalls them in the loop that already derivesmessageJavaLiteral, emittingliteralJavaExpression/guardJavaExpressioninto the parameter graph. A half that does not render is left absent, as the default value literal is.Repositoryand the three REST controllers read the derived keys.ResolvePathSupport.Pathgainsowner/property:expressiontaken apart, for a caller that must describe a read as data rather than emit it as code.Verification
IntentEmissionCoverageITpasses unchanged — it asserts on the generated controller text, not on the model, so it is the end-to-end oracle that the emitted Java is byte-identical. It still finds!(new java.math.BigDecimal(entity.Paid.toString()).compareTo(new java.math.BigDecimal("0")) >= 0)and the to-one guard shapes exactly as before.EdmIntentGeneratorTestcases that asserted on the Java-carrying model now pin the neutral model AND the Java rendered from it, so the two halves cannot drift apart.JavaLiteralsTestcases cover both renderers: number / moment / temporal, the ISO offset in both directions, the hop null guard, thenumericKeycomparison-by-value (intent: requiredWhen's when: guard NPEs the parser on a typeless field, refuses Integer/String spellings, and types a cross-model to-one as integer while its FK is Long #7237), the escape, and every unrenderable input yielding null rather than a weaker guard.ModelParameterProcessorTestcases cover the derivation and its absence.engine-intent(1315) andide-template(158) unit suites green.Test plan
engine-intent+ide-templateunit suitesIntentEmissionCoverageIT(the generated-Java oracle) — green, 178sformatter:formatappliedDownstream
The model shape changes, so every module regenerates. In the BusinessIntents fleet that is 52 occurrences across 18 of 80 modules; no
.intentchanges — the authored layer was already clean. The acceptance test there is thatgrep -rlE 'java\.(math|lang|util|time)\.[A-Z]' --include='*.model' --include='*.edm' .returns nothing.Fixes #7405
🤖 Generated with Claude Code