Reputation: 9384
Apache Commons JEXL has the operator In or Match
, which is written as =~
.
It works as a regex matcher if used with String on the right hand side:
"abcdef" =~ "abc.*"
It works as an in operator if used on an Array of String on the right hand side:
"a" =~ ["a","b","c","d","e","f"]
But how can it be used on an array of regexp, which means an array of string but these strings need to be evaluated by a regex matcher? The below obviously does not interprete the string values in the list as regex and thus returns false where I'd like to have true.
"abcdef" =~ ["abc.*", "a.d.*"]
I'd like to know both how to modify the JexlEngine (likely through Java code) and how to make use of that feature in a Jexl expression.
Upvotes: 2
Views: 295
Reputation: 364
To expand or overload JEXL operators, you can derive JexlArithmetic and implement methods following the JexlOperator convention as documented here. In your case, as an example:
public static class MatchingArithmetic extends JexlArithmetic {
public MatchingArithmetic(boolean astrict) {
super(astrict);
}
public boolean contains(Pattern[] container, String str) {
for(Pattern pattern : container) {
if (pattern.matcher(str).matches()) {
return true;
}
}
return false;
}
}
@Test
void testPatterns() {
final JexlEngine jexl = new JexlBuilder()
.permissions(JexlPermissions.UNRESTRICTED) // need this in Jexl 3.3 and later
.arithmetic(new MatchingArithmetic(true))
.create();
JexlScript script = jexl.createScript("str =~ [~/abc.*/, ~/def.*/]", "str");
assertTrue((boolean) script.execute(null, "abcdef"));
assertTrue((boolean) script.execute(null, "defghi"));
assertFalse((boolean) script.execute(null, "ghijkl"));
}
Upvotes: 1
Reputation: 9384
Based on @henrib's answer I created this code which works for me on Jexl 3.4.0.
First of all I created a JexlArithmetic similar to the other solution, but it needs to override the contains(Object, Object) method:
import java.util.regex.Pattern;
import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class MatchingArithmetic extends JexlArithmetic {
private static final Logger log = LogManager.getLogger();
public MatchingArithmetic(boolean astrict) {
super(astrict);
log.debug("MatchingAlgorithm({})", astrict);
}
public Boolean contains(Object container, Object value) {
log.debug("contains({}, {})", container, value);
if (container != null) {
log.trace("container class: {}", container.getClass());
}
if (value != null) {
log.trace("value class: {}", value.getClass());
}
if (container instanceof Pattern[] patterns) {
log.debug("Pattern matching!");
for(Pattern pattern : patterns) {
if (pattern.matcher(String.valueOf(value)).matches()) {
return true;
}
}
return false;
} else {
return super.contains(container, value);
}
}
}
Then I made use of that class like so:
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlScript;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class JexlTest {
private static final Logger log = LogManager.getLogger();
public static void main(String[] args) {
JexlEngine jexl = new JexlBuilder()
.arithmetic(new MatchingArithmetic(true))
.create();
JexlScript script = jexl.createScript("str =~ [\"abc.*\", \"def.*\"]", "str");
log.info("{}", script.execute(null, "abcdef"));
log.info("{}", script.execute(null, "defghi"));
log.info("{}", script.execute(null, "ghijkl"));
script = jexl.createScript("str =~ [~/abc.*/, ~/def.*/]", "str");
log.info("{}", script.execute(null, "abcdef"));
log.info("{}", script.execute(null, "defghi"));
log.info("{}", script.execute(null, "ghijkl"));
}
}
The output I get:
07:38:42.867 [main] DEBUG MatchingArithmetic - MatchingAlgorithm(true)
07:38:42.906 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], abcdef)
07:38:42.907 [main] INFO JexlTest - false
07:38:42.907 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], defghi)
07:38:42.907 [main] INFO JexlTest - false
07:38:42.907 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], ghijkl)
07:38:42.907 [main] INFO JexlTest - false
07:38:42.908 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], abcdef)
07:38:42.908 [main] DEBUG MatchingArithmetic - Pattern matching!
07:38:42.908 [main] INFO JexlTest - true
07:38:42.908 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], defghi)
07:38:42.908 [main] DEBUG MatchingArithmetic - Pattern matching!
07:38:42.908 [main] INFO JexlTest - true
07:38:42.908 [main] DEBUG MatchingArithmetic - contains([abc.*, def.*], ghijkl)
07:38:42.908 [main] DEBUG MatchingArithmetic - Pattern matching!
07:38:42.908 [main] INFO JexlTest - false
I was not aware the JexlEngine can detect strings as regex patterns if they are enclosed in ~/
and /
. If this is a standard feature, maybe some pattern array matching could be part of the next Jexl release.
Upvotes: 0