queeg
queeg

Reputation: 9384

JEXL expression to match with an array of regex?

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

Answers (2)

henrib
henrib

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

queeg
queeg

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

Related Questions