This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new b70ebb98ef4e CAMEL-25744: camel-jbang - the AI validator reports a 
comparison of CamelTimerCounter that can never be true, as the counter starts 
at 1 (#28040)
b70ebb98ef4e is described below

commit b70ebb98ef4e271c9c249024525c4984b49e3039
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 20:55:50 2026 +0200

    CAMEL-25744: camel-jbang - the AI validator reports a comparison of 
CamelTimerCounter that can never be true, as the counter starts at 1 (#28040)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../dsl/jbang/core/commands/ai/HeaderChecks.java   | 34 +++++++++++++
 .../commands/ai/SourceValidatorEndpointTest.java   | 59 ++++++++++++++++++++++
 2 files changed, 93 insertions(+)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
index 0960cdb14c73..6fc97eedcc48 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java
@@ -57,6 +57,15 @@ final class HeaderChecks {
      */
     static final Map<String, String> METADATA_OPTION = Map.of("timer", 
"includeMetadata");
 
+    /**
+     * A comparison of the timer's counter with a number: the counter counts 
the fires from 1, so {@code == 0} is never
+     * true (CAMEL-25744: in 8 of 10 benchmark runs a model compared it with 0 
to pick the first fire). The Simple forms
+     * (${exchangeProperty.CamelTimerCounter} == 0, ${header.CamelTimerCounter 
== 0 ? ...}) and the Groovy ones
+     * (exchange.getProperty('CamelTimerCounter', Integer) == 0).
+     */
+    static final Pattern TIMER_COUNTER_COMPARISON = Pattern.compile(
+            
"CamelTimerCounter(?:\\}|['\"]\\s*(?:,\\s*[\\w.]+\\s*)?\\))?\\s*(==|!=|<=|<|>=|>)\\s*['\"]?(\\d+)['\"]?");
+
     static final Pattern EXCHANGE_PROPERTY_REF_PATTERN = 
Pattern.compile("\\$\\{exchangeProperty\\.(Camel[A-Z][A-Za-z0-9]*)");
 
     /** Whether the option that makes the component set its exchange 
properties is true somewhere in the content. */
@@ -106,11 +115,36 @@ final class HeaderChecks {
         return null;
     }
 
+    /**
+     * The comparisons of CamelTimerCounter that can never be true (== 0, <= 
0, < 1) or never be false (!= 0, > 0, >=
+     * 1), as the counter is 1 on the first fire.
+     */
+    static List<String> timerCounterComparisons(String content) {
+        List<String> msgs = new ArrayList<>();
+        String[] lines = content.split("\n", -1);
+        for (int i = 0; i < lines.length; i++) {
+            Matcher m = TIMER_COUNTER_COMPARISON.matcher(lines[i]);
+            while (m.find()) {
+                String op = m.group(1);
+                int n = Integer.parseInt(m.group(2));
+                boolean never = ("==".equals(op) || "<=".equals(op)) && n == 0 
|| "<".equals(op) && n <= 1;
+                boolean always = ("!=".equals(op) || ">".equals(op)) && n == 0 
|| ">=".equals(op) && n <= 1;
+                if (never || always) {
+                    msgs.add("Line " + (i + 1) + ": CamelTimerCounter counts 
the fires from 1 (the first fire is 1), so "
+                             + op + " " + n + " is " + (never ? "never true" : 
"always true")
+                             + ": compare with 1 to pick the first fire");
+                }
+            }
+        }
+        return msgs;
+    }
+
     public static List<String> validateKnownHeaders(String content, 
CamelCatalog catalog) {
         List<String> msgs = new ArrayList<>();
         if (content == null) {
             return msgs;
         }
+        msgs.addAll(timerCounterComparisons(content));
         Set<String> known = new LinkedHashSet<>();
         Map<String, String> owner = new LinkedHashMap<>();
         Matcher sm = SCHEME_IN_URI_PATTERN.matcher(content);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
index afaa47ac342c..b64b7f94305e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java
@@ -457,6 +457,65 @@ class SourceValidatorEndpointTest {
         assertThat(msgs.get(1)).startsWith("Line 4:").contains("timer sets 
CamelTimerName only with includeMetadata=true");
     }
 
+    /** CAMEL-25744: the counter is 1 on the first fire, so a comparison with 
0 never picks it. */
+    @Test
+    void aTimerCounterComparisonThatCanNeverBeTrueIsReported() {
+        List<String> msgs = SourceValidator.validateKnownHeaders("""
+                - from:
+                    uri: "timer:tick?repeatCount=2&includeMetadata=true"
+                    steps:
+                      - setBody:
+                          simple: 
"resource:file:${exchangeProperty.CamelTimerCounter == 0 ? 'a.json' : 'b.json'}"
+                      - choice:
+                          when:
+                            - simple: "${header.CamelTimerCounter} != 0"
+                              steps:
+                                - log: "${body}"
+                      - filter:
+                          groovy: "exchange.getProperty('CamelTimerCounter', 
Integer) < 1"
+                """, catalog);
+        assertThat(msgs).hasSize(3);
+        assertThat(msgs.get(0)).startsWith("Line 5:").contains("counts the 
fires from 1").contains("== 0 is never true")
+                .contains("compare with 1");
+        assertThat(msgs.get(1)).startsWith("Line 8:").contains("!= 0 is always 
true");
+        assertThat(msgs.get(2)).startsWith("Line 12:").contains("< 1 is never 
true");
+    }
+
+    @Test
+    void aTimerCounterComparisonThatIsAlwaysTrueIsReported() {
+        List<String> msgs = SourceValidator.validateKnownHeaders("""
+                - from:
+                    uri: "timer:tick?repeatCount=2&includeMetadata=true"
+                    steps:
+                      - filter:
+                          simple: "${exchangeProperty.CamelTimerCounter} > 0"
+                      - filter:
+                          groovy: "exchange.getProperty('CamelTimerCounter', 
Integer) >= 1"
+                      - filter:
+                          simple: "${exchangeProperty.CamelTimerCounter} >= 0"
+                """, catalog);
+        assertThat(msgs).hasSize(3);
+        assertThat(msgs.get(0)).startsWith("Line 5:").contains("> 0 is always 
true");
+        assertThat(msgs.get(1)).startsWith("Line 7:").contains(">= 1 is always 
true");
+        assertThat(msgs.get(2)).startsWith("Line 9:").contains(">= 0 is always 
true");
+    }
+
+    @Test
+    void aTimerCounterComparedWithOneIsFine() {
+        List<String> msgs = SourceValidator.validateKnownHeaders("""
+                - from:
+                    uri: "timer:tick?repeatCount=2&includeMetadata=true"
+                    steps:
+                      - setBody:
+                          simple: 
"resource:file:${exchangeProperty.CamelTimerCounter == 1 ? 'a.json' : 'b.json'}"
+                      - filter:
+                          simple: "${exchangeProperty.CamelTimerCounter} > 1"
+                      - filter:
+                          simple: "${exchangeProperty.CamelTimerCounter} >= 2"
+                """, catalog);
+        assertThat(msgs).isEmpty();
+    }
+
     @Test
     void withIncludeMetadataAHeaderLookupFindsTheExchangeProperty() {
         // Simple's ${header.x} falls back to the exchange properties, so it 
reads the counter

Reply via email to