@@ -45,21 +45,17 @@ static EndpointRuleSet transform(EndpointRuleSet ruleSet) {
4545 CoalesceTransform transform = new CoalesceTransform ();
4646
4747 List <Rule > transformedRules = new ArrayList <>();
48- for (Rule rule : ruleSet .getRules ()) {
49- transformedRules .add (transform .transformRule (rule ));
48+ for (int i = 0 ; i < ruleSet .getRules (). size (); i ++ ) {
49+ transformedRules .add (transform .transformRule (ruleSet . getRules (). get ( i ), "root/ rule[" + i + "]" ));
5050 }
5151
5252 if (LOGGER .isLoggable (Level .INFO )) {
53- StringBuilder msg = new StringBuilder ();
54- msg .append ("\n === Coalescing Transform Complete ===\n " );
55- msg .append ("Total: " ).append (transform .coalesceCount ).append (" coalesced, " );
56- msg .append (transform .cacheHits ).append (" cache hits, " );
57- msg .append (transform .skippedNoZeroValue ).append (" skipped (no zero value), " );
58- msg .append (transform .skippedMultipleUses ).append (" skipped (multiple uses)" );
59- if (!transform .skippedRecordTypes .isEmpty ()) {
60- msg .append ("\n Skipped record-returning functions: " ).append (transform .skippedRecordTypes );
61- }
62- LOGGER .info (msg .toString ());
53+ LOGGER .info (String .format (
54+ "Coalescing: %d coalesced, %d cache hits, %d skipped (no zero), %d skipped (multiple uses)" ,
55+ transform .coalesceCount ,
56+ transform .cacheHits ,
57+ transform .skippedNoZeroValue ,
58+ transform .skippedMultipleUses ));
6359 }
6460
6561 return EndpointRuleSet .builder ()
@@ -69,50 +65,31 @@ static EndpointRuleSet transform(EndpointRuleSet ruleSet) {
6965 .build ();
7066 }
7167
72- private Rule transformRule (Rule rule ) {
73- Set <Condition > eliminatedConditions = new HashSet <>();
74- List <Condition > conditions = rule .getConditions ();
75- Map <String , Integer > localVarUsage = countLocalVariableUsage (conditions );
76- List <Condition > transformedConditions = transformConditions (conditions , eliminatedConditions , localVarUsage );
77-
78- if (rule instanceof TreeRule ) {
79- TreeRule treeRule = (TreeRule ) rule ;
80- List <Rule > transformedNestedRules = new ArrayList <>();
81- boolean nestedChanged = false ;
82-
83- for (Rule nestedRule : treeRule .getRules ()) {
84- Rule transformedNested = transformRule (nestedRule );
85- transformedNestedRules .add (transformedNested );
86- if (transformedNested != nestedRule ) {
87- nestedChanged = true ;
88- }
89- }
90-
91- if (!transformedConditions .equals (conditions ) || nestedChanged ) {
92- return TreeRule .builder ()
93- .description (rule .getDocumentation ().orElse (null ))
94- .conditions (transformedConditions )
95- .treeRule (transformedNestedRules );
68+ private Rule transformRule (Rule rule , String rulePath ) {
69+ // Count local usage for THIS rule's conditions
70+ Map <String , Integer > localVarUsage = new HashMap <>();
71+ for (Condition condition : rule .getConditions ()) {
72+ for (String ref : condition .getFunction ().getReferences ()) {
73+ localVarUsage .merge (ref , 1 , Integer ::sum );
9674 }
97- } else if (!transformedConditions .equals (conditions )) {
98- // For other rule types, just update conditions
99- return rule .withConditions (transformedConditions );
10075 }
10176
102- return rule ;
103- }
104-
105- private Map < String , Integer > countLocalVariableUsage ( List < Condition > conditions ) {
106- Map < String , Integer > usage = new HashMap <>( );
77+ Set < Condition > eliminatedConditions = new HashSet <>() ;
78+ List < Condition > transformedConditions = transformConditions (
79+ rule . getConditions (),
80+ eliminatedConditions ,
81+ localVarUsage );
10782
108- // Count how many times each variable is used within this specific rule
109- for (Condition condition : conditions ) {
110- for (String ref : condition .getFunction ().getReferences ()) {
111- usage .merge (ref , 1 , Integer ::sum );
112- }
83+ if (rule instanceof TreeRule ) {
84+ TreeRule treeRule = (TreeRule ) rule ;
85+ return TreeRule .builder ()
86+ .description (rule .getDocumentation ().orElse (null ))
87+ .conditions (transformedConditions )
88+ .treeRule (TreeRewriter .transformNestedRules (treeRule , rulePath , this ::transformRule ));
11389 }
11490
115- return usage ;
91+ // CoalesceTransform only modifies conditions, not endpoints/errors
92+ return rule .withConditions (transformedConditions );
11693 }
11794
11895 private List <Condition > transformConditions (
@@ -128,25 +105,19 @@ private List<Condition> transformConditions(
128105 continue ;
129106 }
130107
131- // Check if this is a bind that can be coalesced with the next condition
132108 if (i + 1 < conditions .size () && current .getResult ().isPresent ()) {
133109 String var = current .getResult ().get ().toString ();
134110 Condition next = conditions .get (i + 1 );
135111
136112 if (canCoalesce (var , current , next , localVarUsage )) {
137- // Create coalesced condition
138- Condition coalesced = createCoalescedCondition (current , next , var );
139- result .add (coalesced );
140- // Mark both conditions as eliminated
113+ result .add (createCoalescedCondition (current , next , var ));
141114 eliminatedConditions .add (current );
142115 eliminatedConditions .add (next );
143- // Skip the next condition
144- i ++;
116+ i ++; // Skip next
145117 continue ;
146118 }
147119 }
148120
149- // No coalescing possible, keep the condition as-is
150121 result .add (current );
151122 }
152123
@@ -155,28 +126,21 @@ private List<Condition> transformConditions(
155126
156127 private boolean canCoalesce (String var , Condition bind , Condition use , Map <String , Integer > localVarUsage ) {
157128 if (!use .getFunction ().getReferences ().contains (var )) {
158- // The use condition must reference the variable
159129 return false ;
160- } else if (use .getFunction ().getFunctionDefinition () == IsSet .getDefinition ()) {
161- // Never coalesce into presence checks (isSet)
130+ }
131+
132+ if (use .getFunction ().getFunctionDefinition () == IsSet .getDefinition ()) {
162133 return false ;
163134 }
164135
165- // Check if variable is only used once in this local rule context (even if it appears multiple times globally)
166136 Integer localUses = localVarUsage .get (var );
167137 if (localUses == null || localUses > 1 ) {
168138 skippedMultipleUses ++;
169139 return false ;
170140 }
171141
172- // Get the actual return type (could be Optional<T> or T)
173142 Type type = bind .getFunction ().getFunctionDefinition ().getReturnType ();
174-
175- // Check if we can get a zero value for this type. For OptionalType, we use the inner type's zero value
176- Type innerType = type ;
177- if (type instanceof OptionalType ) {
178- innerType = ((OptionalType ) type ).inner ();
179- }
143+ Type innerType = type instanceof OptionalType ? ((OptionalType ) type ).inner () : type ;
180144
181145 if (innerType instanceof RecordType ) {
182146 skippedNoZeroValue ++;
@@ -196,16 +160,10 @@ private Condition createCoalescedCondition(Condition bind, Condition use, String
196160 LibraryFunction bindExpr = bind .getFunction ();
197161 LibraryFunction useExpr = use .getFunction ();
198162
199- // Get the type and its zero value
200163 Type type = bindExpr .getFunctionDefinition ().getReturnType ();
201- Type innerType = type ;
202- if (type instanceof OptionalType ) {
203- innerType = ((OptionalType ) type ).inner ();
204- }
205-
164+ Type innerType = type instanceof OptionalType ? ((OptionalType ) type ).inner () : type ;
206165 Literal zero = innerType .getZeroValue ().get ();
207166
208- // Create cache key based on canonical representations
209167 String bindCanonical = bindExpr .canonicalize ().toString ();
210168 String zeroCanonical = zero .toString ();
211169 String useCanonical = useExpr .canonicalize ().toString ();
@@ -219,12 +177,10 @@ private Condition createCoalescedCondition(Condition bind, Condition use, String
219177 }
220178
221179 Expression coalesced = Coalesce .ofExpressions (bindExpr , zero );
222-
223- // Replace the variable reference in the use expression
224180 Map <String , Expression > replacements = new HashMap <>();
225181 replacements .put (var , coalesced );
226- ReferenceRewriter rewriter = ReferenceRewriter . forReplacements ( replacements );
227- Expression replaced = rewriter .rewrite (useExpr );
182+
183+ Expression replaced = TreeRewriter . forReplacements ( replacements ) .rewrite (useExpr );
228184 LibraryFunction canonicalized = ((LibraryFunction ) replaced ).canonicalize ();
229185
230186 Condition .Builder builder = Condition .builder ().fn (canonicalized );
0 commit comments