Search code examples
javaregexjexl

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.


Solution

  • 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.