Skip to content

Commit

Permalink
[incubator-kie-issues#1409]-Fixed matches function behavior (#6055)
Browse files Browse the repository at this point in the history
  • Loading branch information
bncriju committed Aug 23, 2024
1 parent 20d20f7 commit edc77d5
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -39,40 +44,58 @@ public FEELFnResult<Boolean> invoke(@ParameterName("input") String input, @Param
}

public FEELFnResult<Boolean> 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<Boolean> 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<Character> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<MatchesFunction> 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"));
}
}

}

0 comments on commit edc77d5

Please sign in to comment.