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