diff --git a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/MatchesFunction.java b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/MatchesFunction.java index f6b1806ca20..79ff2a01368 100644 --- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/MatchesFunction.java +++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/MatchesFunction.java @@ -18,16 +18,21 @@ */ package org.kie.dmn.feel.runtime.functions; +import java.security.InvalidParameterException; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity; import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MatchesFunction extends BaseFEELFunction { - + private static final Logger log = LoggerFactory.getLogger(MatchesFunction.class); public static final MatchesFunction INSTANCE = new MatchesFunction(); private MatchesFunction() { @@ -39,40 +44,58 @@ public FEELFnResult invoke(@ParameterName("input") String input, @Param } public FEELFnResult invoke(@ParameterName("input") String input, @ParameterName("pattern") String pattern, @ParameterName("flags") String flags) { - if ( input == null ) { - return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "input", "cannot be null" ) ); - } - if ( pattern == null ) { - return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "pattern", "cannot be null" ) ); - } try { - int f = processFlags( flags ); - Pattern p = Pattern.compile( pattern, f ); - Matcher m = p.matcher( input ); - return FEELFnResult.ofResult( m.find() ); - } catch ( PatternSyntaxException e ) { - return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "pattern", "is invalid and can not be compiled", e ) ); - } catch ( IllegalArgumentException t ) { + return matchFunctionWithFlags(input,pattern,flags); + } catch ( PatternSyntaxException t ) { + return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "pattern", "is invalid and can not be compiled", t ) ); + } catch (InvalidParameterException t ) { + return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, t.getMessage(), "cannot be null", t ) ); + } catch (IllegalArgumentException t ) { return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "flags", "contains unknown flags", t ) ); - } catch ( Throwable t) { + } catch (Throwable t) { return FEELFnResult.ofError( new InvalidParametersEvent( Severity.ERROR, "pattern", "is invalid and can not be compiled", t ) ); } } - private int processFlags(String flags) { - int f = 0; - if( flags != null ) { - if( flags.contains( "s" ) ) { - f |= Pattern.DOTALL; - } - if( flags.contains( "m" ) ) { - f |= Pattern.MULTILINE; - } - if( flags.contains( "i" ) ) { - f |= Pattern.CASE_INSENSITIVE; - } + static FEELFnResult matchFunctionWithFlags(String input, String pattern, String flags) { + log.debug("Input: {} , Pattern: {}, Flags: {}", input, pattern, flags); + if ( input == null ) { + throw new InvalidParameterException("input"); + } + if ( pattern == null ) { + throw new InvalidParameterException("pattern"); } - return f; + final String flagsString; + if (flags != null && !flags.isEmpty()) { + checkFlags(flags); + if(!flags.contains("U")){ + flags += "U"; + } + flagsString = String.format("(?%s)", flags); + } else { + flagsString = ""; + } + log.debug("flagsString: {}", flagsString); + String stringToBeMatched = flagsString + pattern; + log.debug("stringToBeMatched: {}", stringToBeMatched); + Pattern p=Pattern.compile(stringToBeMatched); + Matcher m = p.matcher( input ); + boolean matchFound=m.find(); + log.debug("matchFound: {}", matchFound); + return FEELFnResult.ofResult(matchFound); } + static void checkFlags(String flags) { + Set allowedChars = Set.of('s','i','x','m'); + boolean isValidFlag= flags.chars() + .mapToObj(c -> (char) c) + .allMatch(allowedChars::contains) + && flags.chars() + .mapToObj(c -> (char) c) + .collect(Collectors.toSet()) + .size() == flags.length(); + if(!isValidFlag){ + throw new IllegalArgumentException("Not a valid flag parameter " +flags); + } + } } diff --git a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/MatchesFunctionTest.java b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/MatchesFunctionTest.java index eeb86f511a9..eea47b17de6 100644 --- a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/MatchesFunctionTest.java +++ b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/MatchesFunctionTest.java @@ -19,54 +19,116 @@ package org.kie.dmn.feel.runtime.functions; import org.junit.jupiter.api.Test; -import org.kie.dmn.feel.runtime.events.InvalidParametersEvent; +import org.mockito.MockedStatic; -class MatchesFunctionTest { +import java.security.InvalidParameterException; +import java.util.regex.PatternSyntaxException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; - private final static MatchesFunction matchesFunction = MatchesFunction.INSTANCE; +class MatchesFunctionTest { @Test void invokeNull() { - FunctionTestUtil.assertResultError(matchesFunction.invoke((String) null, null), InvalidParametersEvent.class); - FunctionTestUtil.assertResultError(matchesFunction.invoke(null, "test"), InvalidParametersEvent.class); - FunctionTestUtil.assertResultError(matchesFunction.invoke("test", null), InvalidParametersEvent.class); + assertThrows(InvalidParameterException.class, () -> MatchesFunction.matchFunctionWithFlags(null, null, null)); + assertThrows(InvalidParameterException.class, () -> MatchesFunction.matchFunctionWithFlags(null, "test",null)); + assertThrows(InvalidParameterException.class, () -> MatchesFunction.matchFunctionWithFlags("test", null,null)); } @Test void invokeUnsupportedFlags() { - FunctionTestUtil.assertResult(matchesFunction.invoke("foobar", "fo.bar", "g"), true); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.matchFunctionWithFlags("foobar", "fo.bar", "g")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.matchFunctionWithFlags("abracadabra", "bra", "p")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.matchFunctionWithFlags("abracadabra", "bra", "X")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.matchFunctionWithFlags("abracadabra", "bra", " ")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.matchFunctionWithFlags("abracadabra", "bra", "iU")); } @Test void invokeWithoutFlagsMatch() { - FunctionTestUtil.assertResult(matchesFunction.invoke("test", "test"), true); - FunctionTestUtil.assertResult(matchesFunction.invoke("foobar", "^fo*b"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("test", "test",null), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("foobar", "^fo*b",null), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("abracadabra", "bra", ""), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("abracadabra", "bra",null), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("(?xi)[hello world()]", "hello",null), true); } @Test void invokeWithoutFlagsNotMatch() { - FunctionTestUtil.assertResult(matchesFunction.invoke("test", "testt"), false); - FunctionTestUtil.assertResult(matchesFunction.invoke("foobar", "^fo*bb"), false); - FunctionTestUtil.assertResult(matchesFunction.invoke("fo\nbar", "fo.bar"), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("test", "testt",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("foobar", "^fo*bb",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "fo.bar",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("h", "(.)\3",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("h", "(.)\2",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("input", "\3",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "(?iU)(?iU)(ab)[|cd]",null), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "(?x)(?i)hello world","i"), false); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "(?xi)hello world",null), false); } @Test void invokeWithFlagDotAll() { - FunctionTestUtil.assertResult(matchesFunction.invoke("fo\nbar", "fo.bar", "s"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "fo.bar", "s"), true); } @Test void invokeWithFlagMultiline() { - FunctionTestUtil.assertResult(matchesFunction.invoke("fo\nbar", "^bar", "m"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "^bar", "m"), true); } @Test void invokeWithFlagCaseInsensitive() { - FunctionTestUtil.assertResult(matchesFunction.invoke("foobar", "^Fo*bar", "i"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("foobar", "^Fo*bar", "i"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("foobar", "^Fo*bar", "i"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("\u212A", "k","i"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("\u212A", "K","i"), true); + } + + @Test + void invokeWithFlagComments() { + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("hello world", "hello"+"\"+ sworld", "x"), false); } @Test void invokeWithAllFlags() { - FunctionTestUtil.assertResult(matchesFunction.invoke("fo\nbar", "Fo.^bar", "smi"), true); + FunctionTestUtil.assertResult(MatchesFunction.matchFunctionWithFlags("fo\nbar", "Fo.^bar", "smi"), true); + } + + @Test + void checkForPatternTest() { + assertThrows(PatternSyntaxException.class, () -> MatchesFunction.matchFunctionWithFlags("foobar", "(abc|def(ghi", "i")); + } + + @Test + void checkFlagsTest() { + assertDoesNotThrow(() -> MatchesFunction.checkFlags("s")); + assertDoesNotThrow(() -> MatchesFunction.checkFlags("i")); + assertDoesNotThrow(() -> MatchesFunction.checkFlags("sx")); + assertDoesNotThrow(() -> MatchesFunction.checkFlags("six")); + assertDoesNotThrow(() -> MatchesFunction.checkFlags("sixm")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("a")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("sa")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("siU@")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("siUxU")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("ss")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("siiU")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("si U")); + assertThrows(IllegalArgumentException.class, () -> MatchesFunction.checkFlags("U")); } + + @Test + void checkMatchFunctionWithFlagsInvocation() { + MatchesFunction matchesFunctionSpied = spy(MatchesFunction.INSTANCE); + matchesFunctionSpied.invoke("input", "pattern"); + verify(matchesFunctionSpied, times(1)).invoke("input", "pattern", null); + try (MockedStatic matchesFunctionMocked = mockStatic(MatchesFunction.class)) { + matchesFunctionSpied.invoke("input", "pattern"); + matchesFunctionMocked.verify(() -> MatchesFunction.matchFunctionWithFlags("input", "pattern", null)); + matchesFunctionSpied.invoke("input", "pattern", "flags"); + matchesFunctionMocked.verify(() -> MatchesFunction.matchFunctionWithFlags("input", "pattern", "flags")); + } + } + } \ No newline at end of file