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 5c3f5bdea595 CAMEL-24582: simple language - add equals/!equals
operators
5c3f5bdea595 is described below
commit 5c3f5bdea595cb44881af577da577af2e40c004c
Author: Karol Krawczyk <[email protected]>
AuthorDate: Tue Sep 1 22:17:35 2026 +0200
CAMEL-24582: simple language - add equals/!equals operators
Adds `equals` and `!equals` operators to the simple language, letting a
comparison always be evaluated as text rather than favoring numeric comparison
for all-digit strings (as `==` intentionally does). This lets users compare
identifiers like padded account numbers where leading zeros carry meaning.
`LanguageHelper.equalsString` and `PredicateBuilder.equalsString` mirror
the existing `startsWith`/`endsWith` pair. Simple's tokenizer, operator
constants, and generated catalog metadata are updated accordingly, along with
documentation and tests.
Closes #26022
Co-authored-by: Claude <[email protected]>
---
.../camel/catalog/docs/simple-operators.adoc | 14 ++++++++
.../org/apache/camel/catalog/languages/simple.json | 18 +++++-----
.../camel/catalog/simple/camel-simple-validator.js | 2 ++
.../org/apache/camel/language/simple/simple.json | 18 +++++-----
.../modules/languages/pages/simple-operators.adoc | 14 ++++++++
.../language/simple/SimpleOperatorConstants.java | 12 +++++++
.../camel/language/simple/SimpleTokenizer.java | 24 +++++++------
.../language/simple/ast/BinaryExpression.java | 5 +++
.../language/simple/types/BinaryOperatorType.java | 14 +++++++-
.../camel/language/simple/SimpleOperatorTest.java | 39 ++++++++++++++++++++++
.../org/apache/camel/support/LanguageHelper.java | 25 ++++++++++++++
.../camel/support/builder/PredicateBuilder.java | 13 ++++++++
12 files changed, 170 insertions(+), 28 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
index d1b48abe47d9..76eebd96b0d7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
@@ -45,6 +45,8 @@ The following comparison operators are supported:
|`!contains` | For testing if it does not contain in a string-based value
|`endsWith` | For testing if the left-hand side string ends with the
right-hand string.
|`!endsWith` | For testing if the left-hand side string does not end with the
right-hand string.
+|`equals` | For testing if the left-hand side string is equal to the
right-hand string, compared as text only.
+|`!equals` | For testing if the left-hand side string is not equal to the
right-hand string, compared as text only.
|`in` | For matching if in a set of values, each element must be separated by
comma. If you want to include an empty value, then it must be defined using
double comma, e.g. `",,bronze,silver,gold"`, which is a set of four values with
an empty value and then the three medals.
|`!in` | For matching if not in a set of values, each element must be
separated by comma. If you want to include an empty value, then it must be
defined using double comma, e.g. `",,bronze,silver,gold"`, which is a set of
four values with an empty value and then the three medals.
|`is` | For matching if the left-hand side type is an instance of the value.
@@ -57,6 +59,18 @@ The following comparison operators are supported:
|`!startsWith` | For testing if the left-hand side string does not start with
the right-hand string.
|====
+When both sides of `==` are strings made up only of digits, they are compared
as numbers. That is what makes
+`0001 == 001` true, which is usually what you want for values that happen to
be written with padding, such as
+the hour from `${date:now:HH}`. It is not what you want for identifiers like
account numbers, where the leading
+zeros carry meaning. Use `equals` for those, as it always compares the two
values as text:
+
+[source,java]
+----
+// Account1 is "0001" and Account2 is "001"
+.filter().simple("${header.Account1} == ${header.Account2}") // true,
compared as numbers
+.filter().simple("${header.Account1} equals ${header.Account2}") // false,
compared as text
+----
+
=== Numeric Operators
The following numeric operators can be used:
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
index bf5fdfc80090..6da75471d8fb 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
@@ -203,13 +203,15 @@
"!startsWith": { "index": 21, "kind": "operator", "displayName": "Not
starts with", "label": "binary", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tests whether the left operand string does not start with the right operand
string.", "operatorKind": "binary", "operatorSyntax": "LHS !startsWith RHS",
"precedence": 10, "examples": [ "${header.name} !startsWith 'Camel'" ] },
"endsWith": { "index": 22, "kind": "operator", "displayName": "Ends with",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string ends with the right operand string.", "operatorKind": "binary",
"operatorSyntax": "LHS endsWith RHS", "precedence": 10, "examples": [
"${header.name} endsWith '.xml'" ] },
"!endsWith": { "index": 23, "kind": "operator", "displayName": "Not ends
with", "label": "binary", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tests whether the left operand string does not end with the right operand
string.", "operatorKind": "binary", "operatorSyntax": "LHS !endsWith RHS",
"precedence": 10, "examples": [ "${header.name} !endsWith '.xml'" ] },
- "++": { "index": 24, "kind": "operator", "displayName": "Inc", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Increments the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}++", "precedence": 1,
"examples": [ "${header.count}++" ] },
- "--": { "index": 25, "kind": "operator", "displayName": "Dec", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Decrements the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}--", "precedence": 1,
"examples": [ "${header.count}--" ] },
- "&&": { "index": 26, "kind": "operator", "displayName": "And", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical AND. Both left and
right predicates must evaluate to true.", "operatorKind": "logical",
"operatorSyntax": "predicate && predicate", "precedence": 30, "examples": [
"${header.title} contains 'Camel' && ${header.type} == 'gold'" ] },
- "||": { "index": 27, "kind": "operator", "displayName": "Or", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical OR. At least one
of the left or right predicates must evaluate to true.", "operatorKind":
"logical", "operatorSyntax": "predicate || predicate", "precedence": 30,
"examples": [ "${header.title} contains 'Camel' || ${header.type} == 'gold'" ]
},
- "? :": { "index": 28, "kind": "operator", "displayName": "Ternary",
"label": "ternary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Ternary conditional
operator. Evaluates the predicate and returns trueValue if true, falseValue if
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary",
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25,
"examples": [ "${header.foo} > [...]
- "~>": { "index": 29, "kind": "operator", "displayName": "Chain", "label":
"chain", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Pipes the result of the
left expression as input body to the right expression. Use $param in the right
expression to reference the piped value explicitly.", "operatorKind": "chain",
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~>
${uppercase()}", "${substrin [...]
- "?~>": { "index": 30, "kind": "operator", "displayName": "Chain null
safe", "label": "chain", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Null-safe chain operator. Same as ~> but stops chaining and returns null if
the left expression evaluates to null.", "operatorKind": "chain",
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
- "?:": { "index": 31, "kind": "operator", "displayName": "Elvis", "label":
"other", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Elvis operator
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise
returns the right operand as a fallback value.", "operatorKind": "other",
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+ "equals": { "index": 24, "kind": "operator", "displayName": "Equals",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string equals the right operand string, compared as text without
numeric coercion.", "operatorKind": "binary", "operatorSyntax": "LHS equals
RHS", "precedence": 10, "examples": [ "${header.Account1} equals
${header.Account2}" ] },
+ "!equals": { "index": 25, "kind": "operator", "displayName": "Not equals",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string does not equal the right operand string, compared as text
without numeric coercion.", "operatorKind": "binary", "operatorSyntax": "LHS
!equals RHS", "precedence": 10, "examples": [ "${header.Account1} !equals
${header.Account2}" ] },
+ "++": { "index": 26, "kind": "operator", "displayName": "Inc", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Increments the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}++", "precedence": 1,
"examples": [ "${header.count}++" ] },
+ "--": { "index": 27, "kind": "operator", "displayName": "Dec", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Decrements the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}--", "precedence": 1,
"examples": [ "${header.count}--" ] },
+ "&&": { "index": 28, "kind": "operator", "displayName": "And", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical AND. Both left and
right predicates must evaluate to true.", "operatorKind": "logical",
"operatorSyntax": "predicate && predicate", "precedence": 30, "examples": [
"${header.title} contains 'Camel' && ${header.type} == 'gold'" ] },
+ "||": { "index": 29, "kind": "operator", "displayName": "Or", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical OR. At least one
of the left or right predicates must evaluate to true.", "operatorKind":
"logical", "operatorSyntax": "predicate || predicate", "precedence": 30,
"examples": [ "${header.title} contains 'Camel' || ${header.type} == 'gold'" ]
},
+ "? :": { "index": 30, "kind": "operator", "displayName": "Ternary",
"label": "ternary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Ternary conditional
operator. Evaluates the predicate and returns trueValue if true, falseValue if
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary",
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25,
"examples": [ "${header.foo} > [...]
+ "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label":
"chain", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Pipes the result of the
left expression as input body to the right expression. Use $param in the right
expression to reference the piped value explicitly.", "operatorKind": "chain",
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~>
${uppercase()}", "${substrin [...]
+ "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null
safe", "label": "chain", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Null-safe chain operator. Same as ~> but stops chaining and returns null if
the left expression evaluates to null.", "operatorKind": "chain",
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
+ "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label":
"other", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Elvis operator
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise
returns the right operand as a fallback value.", "operatorKind": "other",
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
index 911aafbc2bd8..1afe01bfc64b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
@@ -182,6 +182,8 @@ const OPERATORS = {
'!startsWith': { kind: 'binary', description: 'Tests whether the left
operand string does not start with the right operand string.' },
'endsWith': { kind: 'binary', description: 'Tests whether the left operand
string ends with the right operand string.' },
'!endsWith': { kind: 'binary', description: 'Tests whether the left operand
string does not end with the right operand string.' },
+ 'equals': { kind: 'binary', description: 'Tests whether the left operand
string equals the right operand string, compared as text without numeric
coercion.' },
+ '!equals': { kind: 'binary', description: 'Tests whether the left operand
string does not equal the right operand string, compared as text without
numeric coercion.' },
'++': { kind: 'unary', description: 'Increments the numeric value by one.
Must immediately follow a function closing brace.' },
'--': { kind: 'unary', description: 'Decrements the numeric value by one.
Must immediately follow a function closing brace.' },
'&&': { kind: 'logical', description: 'Logical AND. Both left and right
predicates must evaluate to true.' },
diff --git
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
index bf5fdfc80090..6da75471d8fb 100644
---
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
+++
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
@@ -203,13 +203,15 @@
"!startsWith": { "index": 21, "kind": "operator", "displayName": "Not
starts with", "label": "binary", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tests whether the left operand string does not start with the right operand
string.", "operatorKind": "binary", "operatorSyntax": "LHS !startsWith RHS",
"precedence": 10, "examples": [ "${header.name} !startsWith 'Camel'" ] },
"endsWith": { "index": 22, "kind": "operator", "displayName": "Ends with",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string ends with the right operand string.", "operatorKind": "binary",
"operatorSyntax": "LHS endsWith RHS", "precedence": 10, "examples": [
"${header.name} endsWith '.xml'" ] },
"!endsWith": { "index": 23, "kind": "operator", "displayName": "Not ends
with", "label": "binary", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tests whether the left operand string does not end with the right operand
string.", "operatorKind": "binary", "operatorSyntax": "LHS !endsWith RHS",
"precedence": 10, "examples": [ "${header.name} !endsWith '.xml'" ] },
- "++": { "index": 24, "kind": "operator", "displayName": "Inc", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Increments the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}++", "precedence": 1,
"examples": [ "${header.count}++" ] },
- "--": { "index": 25, "kind": "operator", "displayName": "Dec", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Decrements the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}--", "precedence": 1,
"examples": [ "${header.count}--" ] },
- "&&": { "index": 26, "kind": "operator", "displayName": "And", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical AND. Both left and
right predicates must evaluate to true.", "operatorKind": "logical",
"operatorSyntax": "predicate && predicate", "precedence": 30, "examples": [
"${header.title} contains 'Camel' && ${header.type} == 'gold'" ] },
- "||": { "index": 27, "kind": "operator", "displayName": "Or", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical OR. At least one
of the left or right predicates must evaluate to true.", "operatorKind":
"logical", "operatorSyntax": "predicate || predicate", "precedence": 30,
"examples": [ "${header.title} contains 'Camel' || ${header.type} == 'gold'" ]
},
- "? :": { "index": 28, "kind": "operator", "displayName": "Ternary",
"label": "ternary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Ternary conditional
operator. Evaluates the predicate and returns trueValue if true, falseValue if
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary",
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25,
"examples": [ "${header.foo} > [...]
- "~>": { "index": 29, "kind": "operator", "displayName": "Chain", "label":
"chain", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Pipes the result of the
left expression as input body to the right expression. Use $param in the right
expression to reference the piped value explicitly.", "operatorKind": "chain",
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~>
${uppercase()}", "${substrin [...]
- "?~>": { "index": 30, "kind": "operator", "displayName": "Chain null
safe", "label": "chain", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Null-safe chain operator. Same as ~> but stops chaining and returns null if
the left expression evaluates to null.", "operatorKind": "chain",
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
- "?:": { "index": 31, "kind": "operator", "displayName": "Elvis", "label":
"other", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Elvis operator
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise
returns the right operand as a fallback value.", "operatorKind": "other",
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+ "equals": { "index": 24, "kind": "operator", "displayName": "Equals",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string equals the right operand string, compared as text without
numeric coercion.", "operatorKind": "binary", "operatorSyntax": "LHS equals
RHS", "precedence": 10, "examples": [ "${header.Account1} equals
${header.Account2}" ] },
+ "!equals": { "index": 25, "kind": "operator", "displayName": "Not equals",
"label": "binary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Tests whether the left
operand string does not equal the right operand string, compared as text
without numeric coercion.", "operatorKind": "binary", "operatorSyntax": "LHS
!equals RHS", "precedence": 10, "examples": [ "${header.Account1} !equals
${header.Account2}" ] },
+ "++": { "index": 26, "kind": "operator", "displayName": "Inc", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Increments the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}++", "precedence": 1,
"examples": [ "${header.count}++" ] },
+ "--": { "index": 27, "kind": "operator", "displayName": "Dec", "label":
"unary", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Decrements the numeric
value by one. Must immediately follow a function closing brace.",
"operatorKind": "unary", "operatorSyntax": "${fn}--", "precedence": 1,
"examples": [ "${header.count}--" ] },
+ "&&": { "index": 28, "kind": "operator", "displayName": "And", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical AND. Both left and
right predicates must evaluate to true.", "operatorKind": "logical",
"operatorSyntax": "predicate && predicate", "precedence": 30, "examples": [
"${header.title} contains 'Camel' && ${header.type} == 'gold'" ] },
+ "||": { "index": 29, "kind": "operator", "displayName": "Or", "label":
"logical", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Logical OR. At least one
of the left or right predicates must evaluate to true.", "operatorKind":
"logical", "operatorSyntax": "predicate || predicate", "precedence": 30,
"examples": [ "${header.title} contains 'Camel' || ${header.type} == 'gold'" ]
},
+ "? :": { "index": 30, "kind": "operator", "displayName": "Ternary",
"label": "ternary", "required": false, "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Ternary conditional
operator. Evaluates the predicate and returns trueValue if true, falseValue if
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary",
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25,
"examples": [ "${header.foo} > [...]
+ "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label":
"chain", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Pipes the result of the
left expression as input body to the right expression. Use $param in the right
expression to reference the piped value explicitly.", "operatorKind": "chain",
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~>
${uppercase()}", "${substrin [...]
+ "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null
safe", "label": "chain", "required": false, "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Null-safe chain operator. Same as ~> but stops chaining and returns null if
the left expression evaluates to null.", "operatorKind": "chain",
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
+ "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label":
"other", "required": false, "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Elvis operator
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise
returns the right operand as a fallback value.", "operatorKind": "other",
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
}
}
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
index d1b48abe47d9..76eebd96b0d7 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
@@ -45,6 +45,8 @@ The following comparison operators are supported:
|`!contains` | For testing if it does not contain in a string-based value
|`endsWith` | For testing if the left-hand side string ends with the
right-hand string.
|`!endsWith` | For testing if the left-hand side string does not end with the
right-hand string.
+|`equals` | For testing if the left-hand side string is equal to the
right-hand string, compared as text only.
+|`!equals` | For testing if the left-hand side string is not equal to the
right-hand string, compared as text only.
|`in` | For matching if in a set of values, each element must be separated by
comma. If you want to include an empty value, then it must be defined using
double comma, e.g. `",,bronze,silver,gold"`, which is a set of four values with
an empty value and then the three medals.
|`!in` | For matching if not in a set of values, each element must be
separated by comma. If you want to include an empty value, then it must be
defined using double comma, e.g. `",,bronze,silver,gold"`, which is a set of
four values with an empty value and then the three medals.
|`is` | For matching if the left-hand side type is an instance of the value.
@@ -57,6 +59,18 @@ The following comparison operators are supported:
|`!startsWith` | For testing if the left-hand side string does not start with
the right-hand string.
|====
+When both sides of `==` are strings made up only of digits, they are compared
as numbers. That is what makes
+`0001 == 001` true, which is usually what you want for values that happen to
be written with padding, such as
+the hour from `${date:now:HH}`. It is not what you want for identifiers like
account numbers, where the leading
+zeros carry meaning. Use `equals` for those, as it always compares the two
values as text:
+
+[source,java]
+----
+// Account1 is "0001" and Account2 is "001"
+.filter().simple("${header.Account1} == ${header.Account2}") // true,
compared as numbers
+.filter().simple("${header.Account1} equals ${header.Account2}") // false,
compared as text
+----
+
=== Numeric Operators
The following numeric operators can be used:
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
index dfbca7987d91..658878ea5778 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
@@ -167,6 +167,18 @@ public final class SimpleOperatorConstants {
annotations = { "kind=binary", "syntax=LHS !endsWith RHS",
"precedence=10" })
public static final String NOT_ENDS_WITH = "!endsWith";
+ @Metadata(description = "Tests whether the left operand string equals the
right operand string, compared as text without numeric coercion.",
+ label = "binary",
+ examples = { "${header.Account1} equals ${header.Account2}" },
+ annotations = { "kind=binary", "syntax=LHS equals RHS",
"precedence=10" })
+ public static final String EQUALS = "equals";
+
+ @Metadata(description = "Tests whether the left operand string does not
equal the right operand string, compared as text without numeric coercion.",
+ label = "binary",
+ examples = { "${header.Account1} !equals ${header.Account2}" },
+ annotations = { "kind=binary", "syntax=LHS !equals RHS",
"precedence=10" })
+ public static final String NOT_EQUALS = "!equals";
+
// --- Unary operators (precedence 1) ---
@Metadata(description = "Increments the numeric value by one. Must
immediately follow a function closing brace.",
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
index c8b4e149d698..4b4ae0956b67 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
@@ -27,7 +27,7 @@ import org.apache.camel.util.ObjectHelper;
public class SimpleTokenizer {
// keep this number in sync with tokens list
- private static final int NUMBER_OF_TOKENS = 54;
+ private static final int NUMBER_OF_TOKENS = 56;
private static final SimpleTokenType[] KNOWN_TOKENS = new
SimpleTokenType[NUMBER_OF_TOKENS];
@@ -84,30 +84,32 @@ public class SimpleTokenizer {
KNOWN_TOKENS[41] = new SimpleTokenType(TokenType.binaryOperator,
"endsWith");
KNOWN_TOKENS[42] = new SimpleTokenType(TokenType.binaryOperator, "ends
with");
KNOWN_TOKENS[43] = new SimpleTokenType(TokenType.binaryOperator,
"!endsWith");
+ KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.binaryOperator,
"!equals");
+ KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.binaryOperator,
"equals");
// other operators
- KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.otherOperator, "?:");
+ KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.otherOperator, "?:");
// chain operators
- KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.chainOperator, "~>");
- KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.chainOperator, "?~>");
+ KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.chainOperator, "~>");
+ KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.chainOperator, "?~>");
// unary operators
- KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.unaryOperator, "++");
- KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.unaryOperator, "--");
+ KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.unaryOperator, "++");
+ KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.unaryOperator, "--");
// logical operators
- KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.logicalOperator,
"&&");
- KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.logicalOperator,
"||");
+ KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.logicalOperator,
"&&");
+ KNOWN_TOKENS[52] = new SimpleTokenType(TokenType.logicalOperator,
"||");
// ternary operators
- KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.ternaryOperator, "?");
- KNOWN_TOKENS[52] = new SimpleTokenType(TokenType.ternaryOperator, ":");
+ KNOWN_TOKENS[53] = new SimpleTokenType(TokenType.ternaryOperator, "?");
+ KNOWN_TOKENS[54] = new SimpleTokenType(TokenType.ternaryOperator, ":");
//binary operator
// it is added as the last item because unary -- has the priority
// if unary not found it is highly possible - operator is run into.
- KNOWN_TOKENS[53] = new SimpleTokenType(TokenType.minusValue, "-");
+ KNOWN_TOKENS[55] = new SimpleTokenType(TokenType.minusValue, "-");
}
/**
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
index 9c515c6077db..46ce040e1c1f 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
@@ -133,6 +133,11 @@ public class BinaryExpression extends BaseSimpleNode {
} else if (operator == BinaryOperatorType.NOT_ENDS_WITH) {
return createExpression(camelContext, leftExp, rightExp,
PredicateBuilder.not(PredicateBuilder.endsWith(leftExp,
rightExp)));
+ } else if (operator == BinaryOperatorType.EQUALS) {
+ return createExpression(camelContext, leftExp, rightExp,
PredicateBuilder.equalsString(leftExp, rightExp));
+ } else if (operator == BinaryOperatorType.NOT_EQUALS) {
+ return createExpression(camelContext, leftExp, rightExp,
+
PredicateBuilder.not(PredicateBuilder.equalsString(leftExp, rightExp)));
}
throw new SimpleParserException("Unknown binary operator " + operator,
token.getIndex());
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java
index fb82fc584a0e..baa7e36e5c09 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/BinaryOperatorType.java
@@ -47,7 +47,9 @@ public enum BinaryOperatorType {
STARTS_WITH,
NOT_STARTS_WITH,
ENDS_WITH,
- NOT_ENDS_WITH;
+ NOT_ENDS_WITH,
+ EQUALS,
+ NOT_EQUALS;
private static final Logger LOG =
LoggerFactory.getLogger(BinaryOperatorType.class);
@@ -121,6 +123,10 @@ public enum BinaryOperatorType {
return NOT_STARTS_WITH;
} else if ("!endsWith".equals(text)) {
return NOT_ENDS_WITH;
+ } else if ("equals".equals(text)) {
+ return EQUALS;
+ } else if ("!equals".equals(text)) {
+ return NOT_EQUALS;
}
throw new IllegalArgumentException("Operator not supported: " + text);
}
@@ -174,6 +180,10 @@ public enum BinaryOperatorType {
return "endsWith";
} else if (operator == NOT_ENDS_WITH) {
return "!endsWith";
+ } else if (operator == EQUALS) {
+ return "equals";
+ } else if (operator == NOT_EQUALS) {
+ return "!equals";
}
return "";
}
@@ -278,6 +288,8 @@ public enum BinaryOperatorType {
return null;
} else if (operator == ENDS_WITH || operator == NOT_ENDS_WITH) {
return null;
+ } else if (operator == EQUALS || operator == NOT_EQUALS) {
+ return null;
}
return null;
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
index dd1539b0beb3..d7272c4928ce 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
@@ -831,6 +831,45 @@ public class SimpleOperatorTest extends
LanguageTestSupport {
assertPredicate("${in.body} !endsWith 'Hi'", true);
}
+ @Test
+ public void testEquals() {
+ exchange.getIn().setHeader("Account1", "0001");
+ exchange.getIn().setHeader("Account2", "001");
+ // == favours numeric comparison when both sides are all digits
+ assertPredicate("${in.header.Account1} == ${in.header.Account2}",
true);
+ // equals compares as text, so the leading zeros are significant
+ assertPredicate("${in.header.Account1} equals ${in.header.Account2}",
false);
+ assertPredicate("${in.header.Account1} equals ${in.header.Account1}",
true);
+ assertPredicate("${in.header.Account1} equals '0001'", true);
+ assertPredicate("${in.header.Account1} equals '001'", false);
+ assertPredicate("${in.header.Account1} equals 1", false);
+
+ exchange.getIn().setBody("Hello there");
+ assertPredicate("${in.body} equals 'Hello there'", true);
+ assertPredicate("${in.body} equals 'hello there'", false);
+ assertPredicate("${in.body} equals 'Hello'", false);
+ }
+
+ @Test
+ public void testNotEquals() {
+ exchange.getIn().setHeader("Account1", "0001");
+ exchange.getIn().setHeader("Account2", "001");
+ assertPredicate("${in.header.Account1} !equals ${in.header.Account2}",
true);
+ assertPredicate("${in.header.Account1} !equals ${in.header.Account1}",
false);
+ assertPredicate("${in.header.Account1} !equals '0001'", false);
+
+ exchange.getIn().setBody("Hello there");
+ assertPredicate("${in.body} !equals 'Hello there'", false);
+ assertPredicate("${in.body} !equals 'hello there'", true);
+ }
+
+ @Test
+ public void testEqualsAsLiteralText() {
+ // only parsed as an operator in a predicate, elsewhere it is ordinary
text
+ assertExpression("the value equals something", "the value equals
something");
+ assertExpression("Logging equals start", "Logging equals start");
+ }
+
@Test
public void testElvis() {
exchange.getIn().setBody(false);
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java
b/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java
index f9cc5123e579..9cd4e6706511 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/LanguageHelper.java
@@ -80,6 +80,31 @@ public final class LanguageHelper {
return ExceptionHelper.stackTraceToString(exception);
}
+ /**
+ * Tests if two values are equal as text, without the numeric coercion
that {@code ==} applies to all-digit strings.
+ *
+ * @param exchange the exchange to test
+ * @param leftValue the value being tested
+ * @param rightValue the value to test against
+ * @return true if both convert to the same string, or false
otherwise
+ */
+ public static boolean equalsString(Exchange exchange, Object leftValue,
Object rightValue) {
+ if (leftValue == null && rightValue == null) {
+ // they are equal
+ return true;
+ } else if (leftValue == null || rightValue == null) {
+ // only one of them is null so they are not equal
+ return false;
+ }
+ String leftStr =
exchange.getContext().getTypeConverter().convertTo(String.class, leftValue);
+ String rightStr =
exchange.getContext().getTypeConverter().convertTo(String.class, rightValue);
+ if (leftStr != null && rightStr != null) {
+ return leftStr.equals(rightStr);
+ } else {
+ return false;
+ }
+ }
+
/**
* Tests if the exchange ends with a given value
*
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
index f2a8cf3104f0..cac09ae1dea3 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
@@ -505,6 +505,19 @@ public class PredicateBuilder {
};
}
+ public static Predicate equalsString(final Expression left, final
Expression right) {
+ return new BinaryPredicateSupport(left, right) {
+
+ protected boolean matches(Exchange exchange, Object leftValue,
Object rightValue) {
+ return LanguageHelper.equalsString(exchange, leftValue,
rightValue);
+ }
+
+ protected String getOperationText() {
+ return "equals";
+ }
+ };
+ }
+
public static Predicate endsWith(final Expression left, final Expression
right) {
return new BinaryPredicateSupport(left, right) {