This is an automated email from the ASF dual-hosted git repository.

gitgabrio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new fbb6c4c7a2 [incubator-kie-issues#1370] DMN: refactor BaseFEELFunction 
getCandidateMethod (#6023)
fbb6c4c7a2 is described below

commit fbb6c4c7a2fd8735bf51fd5dceed871e87c9fe09
Author: Gabriele Cardosi <[email protected]>
AuthorDate: Wed Jul 17 09:51:22 2024 +0200

    [incubator-kie-issues#1370] DMN: refactor BaseFEELFunction 
getCandidateMethod (#6023)
    
    * [incubator-kie-issues#1370] Clean up signature/unused code
    
    * [incubator-kie-issues#1370] WIP. Implemented first unit test
    
    * [incubator-kie-issues#1370] WIP. Implemented unit tests
    
    * [incubator-kie-issues#1370] WIP. Further refactoring with unit tests. 
Fully working
    
    * [incubator-kie-issues#1370] WIP. Begin ScorerHelper implementation. Fully 
working
    
    * [incubator-kie-issues#1370] WIP. Implemented ScorerHelper with test. 
Fully working
    
    * [incubator-kie-issues#1370] WIP. TODO: fix score logic - corner cases not 
covered. Issues with singleton list of null element
    
    * [incubator-kie-issues#1370] Working. Implemented tests.
    
    * [incubator-kie-issues#1370] Fully working. Fixing corner cases with 
Object parameters
    
    * [incubator-kie-issues#1370] Implemented CustomFunction invocation test
    
    * [incubator-kie-issues#1370] Improvement based on PR suggestion
    
    * [incubator-kie-issues#1370] Fix formatting
    
    * [incubator-kie-issues#1370] Benchmark improving - refactoring
    
    ---------
    
    Co-authored-by: Gabriele-Cardosi <[email protected]>
---
 .../dmn/feel/runtime/functions/AllFunction.java    |   1 -
 .../feel/runtime/functions/BaseFEELFunction.java   | 389 ++++---------
 .../runtime/functions/BaseFEELFunctionHelper.java  | 318 +++++++++++
 .../dmn/feel/runtime/functions/ScoreHelper.java    | 237 ++++++++
 .../java/org/kie/dmn/feel/util/CoerceUtil.java     |  18 +
 .../functions/BaseFEELFunctionHelperTest.java      | 378 +++++++++++++
 .../runtime/functions/BaseFEELFunctionTest.java    | 328 +++++++++++
 .../feel/runtime/functions/ScorerHelperTest.java   | 610 +++++++++++++++++++++
 .../java/org/kie/dmn/feel/util/CoerceUtilTest.java |  41 ++
 .../kie-dmn-feel/src/test/resources/logback.xml    |   1 +
 .../feel/runtime/functions/AvgFunction.java        |   2 +-
 .../feel/runtime/functions/ConcatFunction.java     |   7 +-
 12 files changed, 2056 insertions(+), 274 deletions(-)

diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/AllFunction.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/AllFunction.java
index fb6bc4cdda..1ae19f9ceb 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/AllFunction.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/AllFunction.java
@@ -67,7 +67,6 @@ public class AllFunction
             // Arrays.asList does not accept null as parameter
             return FEELFnResult.ofError(new 
InvalidParametersEvent(Severity.ERROR, "b", "cannot be null"));
         }
-        
         return invoke( Arrays.asList( list ) );
     }
 }
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java
index 7dcb0a5b26..33922d3450 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunction.java
@@ -7,7 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
@@ -18,15 +18,11 @@
  */
 package org.kie.dmn.feel.runtime.functions;
 
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -43,23 +39,20 @@ import org.kie.dmn.feel.runtime.FEELFunction;
 import org.kie.dmn.feel.runtime.events.FEELEventBase;
 import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
 import org.kie.dmn.feel.util.Either;
-import org.kie.dmn.feel.util.NumberEvalHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.kie.dmn.feel.util.CoerceUtil.coerceParams;
-
 public abstract class BaseFEELFunction
         implements FEELFunction {
 
-    private final Logger logger = LoggerFactory.getLogger( getClass() );
+    private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private String name;
     private Symbol symbol;
 
     public BaseFEELFunction(String name) {
         this.name = name;
-        this.symbol = new FunctionSymbol( name, this );
+        this.symbol = new FunctionSymbol(name, this);
     }
 
     @Override
@@ -69,7 +62,7 @@ public abstract class BaseFEELFunction
 
     public void setName(String name) {
         this.name = name;
-        ((FunctionSymbol) this.symbol).setId( name );
+        ((FunctionSymbol) this.symbol).setId(name);
     }
 
     @Override
@@ -82,59 +75,71 @@ public abstract class BaseFEELFunction
         // use reflection to call the appropriate invoke method
         try {
             boolean isNamedParams = params.length > 0 && params[0] instanceof 
NamedParameter;
-            if ( !isCustomFunction() ) {
-                List<String> available = null;
-                if ( isNamedParams ) {
-                    available = Stream.of( params ).map( p -> 
((NamedParameter) p).getName() ).collect( Collectors.toList() );
-                }
+            if (!isCustomFunction()) {
 
+                CandidateMethod cm = getCandidateMethod(ctx, params, 
isNamedParams);
 
-                CandidateMethod cm = getCandidateMethod( ctx, params, 
isNamedParams, available );
+                if (cm != null) {
+                    Object result = cm.actualMethod.invoke(this, 
cm.actualParams);
 
-                if ( cm != null ) {
-                    Object result = cm.apply.invoke( this, cm.actualParams );
-
-                    if ( result instanceof Either ) {
+                    if (result instanceof Either) {
                         @SuppressWarnings("unchecked")
                         Either<FEELEvent, Object> either = (Either<FEELEvent, 
Object>) result;
                         return getEitherResult(ctx,
                                                either,
-                                                        () -> 
Stream.of(cm.apply.getParameters()).map(p -> 
p.getAnnotation(ParameterName.class).value()).collect(Collectors.toList()),
-                                                        () -> 
Arrays.asList(cm.actualParams));
+                                               () -> 
Stream.of(cm.actualMethod.getParameters()).map(p -> 
p.getAnnotation(ParameterName.class).value()).collect(Collectors.toList()),
+                                               () -> 
Arrays.asList(cm.actualParams));
                     }
 
                     return result;
                 } else {
-                    // CandidateMethod cm could be null also if reflection 
failed on Platforms not supporting getClass().getDeclaredMethods()
+                    // CandidateMethod cm could be null also if reflection 
failed on Platforms not supporting
+                    // getClass().getDeclaredMethods()
                     String ps = getClass().toString();
-                    logger.error( "Unable to find function '" + getName() + "( 
" + ps.substring( 1, ps.length() - 1 ) + " )'" );
-                    ctx.notifyEvt(() -> new FEELEventBase(Severity.ERROR, 
"Unable to find function '" + getName() + "( " + ps.substring(1, ps.length() - 
1) + " )'", null));
+                    logger.error("Unable to find function '" + getName() + "( 
" + ps.substring(1, ps.length() - 1) +
+                                         " )'");
+                    ctx.notifyEvt(() -> new FEELEventBase(Severity.ERROR, 
"Unable to find function '" + getName() +
+                            "( " + ps.substring(1, ps.length() - 1) + " )'", 
null));
                 }
             } else {
-                if ( isNamedParams ) {
-                    params = rearrangeParameters(params, 
this.getParameters().get(0).stream().map(Param::getName).collect(Collectors.toList()));
+                if (isNamedParams) {
+                    // This is inherently frail because it expects that, if, 
the first parameter is NamedParameter
+                    // and the function is a CustomFunction, then all 
parameters are NamedParameter
+                    NamedParameter[] namedParams =
+                            
Arrays.stream(params).map(NamedParameter.class::cast).toArray(NamedParameter[]::new);
+                    params = 
BaseFEELFunctionHelper.rearrangeParameters(namedParams,
+                                                                        
this.getParameters().get(0).stream().map(Param::getName).collect(Collectors.toList()));
                 }
-                Object result = invoke( ctx, params );
-                if ( result instanceof Either ) {
+                Object result = invoke(ctx, params);
+                if (result instanceof Either) {
                     @SuppressWarnings("unchecked")
                     Either<FEELEvent, Object> either = (Either<FEELEvent, 
Object>) result;
 
                     final Object[] usedParams = params;
                     Object eitherResult = getEitherResult(ctx,
                                                           either,
-                                                          () -> IntStream.of( 
0, usedParams.length ).mapToObj( i -> "arg" + i ).collect( Collectors.toList() 
),
-                                                          () -> Arrays.asList( 
usedParams ));
-                    return normalizeResult( eitherResult );
+                                                          () -> 
IntStream.of(0, usedParams.length).mapToObj(i -> "arg"
+                                                                  + 
i).collect(Collectors.toList()),
+                                                          () -> 
Arrays.asList(usedParams));
+                    return 
BaseFEELFunctionHelper.normalizeResult(eitherResult);
                 }
-                return normalizeResult( result );
+                return BaseFEELFunctionHelper.normalizeResult(result);
             }
-        } catch ( Exception e ) {
-            logger.error( "Error trying to call function " + getName() + ".", 
e );
-            ctx.notifyEvt( () -> new FEELEventBase( Severity.ERROR, "Error 
trying to call function " + getName() + ".", e ));
+        } catch (Exception e) {
+            logger.error("Error trying to call function " + getName() + ".", 
e);
+            ctx.notifyEvt(() -> new FEELEventBase(Severity.ERROR, "Error 
trying to call function " + getName() + ".",
+                                                  e));
         }
         return null;
     }
 
+    @Override
+    public List<List<Param>> getParameters() {
+        // TODO: we could implement this method using reflection, just for 
consistency,
+        // but it is not used at the moment
+        return Collections.emptyList();
+    }
+
     /**
      * this method should be overriden by custom function implementations that 
should be invoked reflectively
      * @param ctx
@@ -142,274 +147,120 @@ public abstract class BaseFEELFunction
      * @return
      */
     public Object invoke(EvaluationContext ctx, Object[] params) {
-        throw new RuntimeException( "This method should be overriden by 
classes that implement custom feel functions" );
-    }
-
-    private Object getEitherResult(EvaluationContext ctx, Either<FEELEvent, 
Object> source, Supplier<List<String>> parameterNamesSupplier,
-                                    Supplier<List<Object>> 
parameterValuesSupplier) {
-        return source.cata((left) -> {
-            ctx.notifyEvt(() -> {
-                              if (left instanceof InvalidParametersEvent 
invalidParametersEvent) {
-                                  
invalidParametersEvent.setNodeName(getName());
-                                  
invalidParametersEvent.setActualParameters(parameterNamesSupplier.get(),
-                                                                             
parameterValuesSupplier.get());
-                              }
-                              return left;
-                          }
-            );
-            return null;
-        }, Function.identity());
+        throw new RuntimeException("This method should be overriden by classes 
that implement custom feel functions");
     }
 
-    private Object[] rearrangeParameters(Object[] params, List<String> pnames) 
{
-        if ( pnames.size() > 0 ) {
-            Object[] actualParams = new Object[pnames.size()];
-            for ( int i = 0; i < actualParams.length; i++ ) {
-                for ( int j = 0; j < params.length; j++ ) {
-                    if ( ((NamedParameter) params[j]).getName().equals( 
pnames.get( i ) ) ) {
-                        actualParams[i] = ((NamedParameter) 
params[j]).getValue();
-                        break;
-                    }
+    /**
+     * @param ctx
+     * @param originalInput
+     * @param isNamedParams <code>true</code> if the parameter refers to value 
to be retrieved inside
+     * <code>ctx</code>; <code>false</code> if the parameter is the actual 
value
+     * @return
+     */
+    protected CandidateMethod getCandidateMethod(EvaluationContext ctx, 
Object[] originalInput, boolean isNamedParams) {
+        CandidateMethod toReturn = null;
+        for (Method method : getClass().getDeclaredMethods()) {
+            if (Modifier.isPublic(method.getModifiers()) && 
method.getName().equals("invoke")) {
+                CandidateMethod candidateMethod = getCandidateMethod(ctx, 
originalInput, isNamedParams, method);
+                if (candidateMethod == null) {
+                    continue;
+                }
+                if (toReturn == null) {
+                    toReturn = candidateMethod;
+                } else if (candidateMethod.score > toReturn.score) {
+                    toReturn = candidateMethod;
+                } else if (candidateMethod.score == toReturn.score) {
+                    toReturn = getBestScoredCandidateMethod(originalInput, 
candidateMethod, toReturn);
                 }
             }
-            params = actualParams;
         }
-        return params;
+        return toReturn;
     }
 
-    private CandidateMethod getCandidateMethod(EvaluationContext ctx, Object[] 
params, boolean isNamedParams, List<String> available) {
-        CandidateMethod candidate = null;
-        // first, look for exact matches
-        for ( Method m : getClass().getDeclaredMethods() ) {
-            if ( !Modifier.isPublic(m.getModifiers()) || !m.getName().equals( 
"invoke" ) ) {
-                continue;
-            }
-
-            Object[] actualParams;
-            boolean injectCtx = Arrays.stream( m.getParameterTypes() 
).anyMatch( p -> EvaluationContext.class.isAssignableFrom( p ) );
-            if( injectCtx ) {
-                actualParams = new Object[ params.length + 1 ];
-                int j = 0;
-                for (int i = 0; i < m.getParameterCount(); i++) {
-                    if( EvaluationContext.class.isAssignableFrom( 
m.getParameterTypes()[i] ) ) {
-                        if( isNamedParams ) {
-                            actualParams[i] = new NamedParameter( "ctx", ctx );
-                        } else {
-                            actualParams[i] = ctx;
-                        }
-                    } else if (j < params.length) {
-                        actualParams[i] = params[j];
-                        j++;
-                    }
-                }
-            } else {
-                actualParams = params;
-            }
-            if( isNamedParams ) {
-                actualParams = calculateActualParams( ctx, m, actualParams, 
available );
-                if( actualParams == null ) {
-                    // incompatible method
-                    continue;
-                }
-            }
-            CandidateMethod cm = new CandidateMethod( actualParams );
-
-            Class<?>[] parameterTypes = m.getParameterTypes();
-            if (!isNamedParams && actualParams.length > 0) {
-                // if named parameters, then it has been adjusted already in 
the calculateActualParams method,
-                // otherwise adjust here
-                adjustForVariableParameters( cm, parameterTypes );
-            }
-
-            if ( parameterTypes.length != cm.getActualParams().length ) {
-                continue;
-            }
+    private CandidateMethod getCandidateMethod(EvaluationContext ctx, Object[] 
originalInput,
+                                               boolean isNamedParams, Method 
m) {
+        Object[] adaptedInput = 
BaseFEELFunctionHelper.getAdjustedParametersForMethod(ctx, originalInput,
+                                                                               
       isNamedParams, m);
+        if (adaptedInput == null) {
+            // incompatible method
+            return null;
+        }
 
-            boolean found = true;
-            for ( int i = 0; i < parameterTypes.length; i++ ) {
-                Class<?> currentIdxActualParameterType = 
cm.getActualClasses()[i];
-                Class<?> expectedParameterType = parameterTypes[i];
-                if ( currentIdxActualParameterType != null && 
!expectedParameterType.isAssignableFrom( currentIdxActualParameterType ) ) {
-                    Optional<Object[]> coercedParams = 
coerceParams(currentIdxActualParameterType, expectedParameterType, 
actualParams, i);
-                    if (coercedParams.isPresent()) {
-                        cm.setActualParams(coercedParams.get());
-                        continue;
-                    }
-                    found = false;
-                    break;
-                }
-            }
-            if ( found ) {
-                cm.setApply( m );
-                if (candidate == null) {
-                    candidate = cm;
-                } else {
-                    if (cm.getScore() > candidate.getScore()) {
-                        candidate = cm;
-                    } else if (cm.getScore() == candidate.getScore()) {
-                        if (isNamedParams && 
nullCount(cm.actualParams)<nullCount(candidate.actualParams)) {
-                            candidate = cm; // `cm` narrower for named 
parameters without need of passing nulls.
-                        } else if 
(candidate.getApply().getParameterTypes().length == 1
-                                && cm.getApply().getParameterTypes().length == 
1
-                                && 
candidate.getApply().getParameterTypes()[0].equals(Object.class)
-                                && 
!cm.getApply().getParameterTypes()[0].equals(Object.class)) {
-                            candidate = cm; // `cm` is more narrowed, hence 
reflect `candidate` to be now `cm`.
-                        }
-                    } else {
-                        // do nothing.
-                    }
-                }
-            }
+        Class<?>[] parameterTypes = m.getParameterTypes();
+        if (parameterTypes.length != adaptedInput.length) {
+            return null;
         }
-        return candidate;
-    }
-    
-    private static long nullCount(Object[] params) {
-        return Stream.of(params).filter(x -> x == null).count();
-    }
 
-    @Override
-    public List<List<Param>> getParameters() {
-        // TODO: we could implement this method using reflection, just for 
consistency,
-        // but it is not used at the moment
-        return Collections.emptyList();
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, adaptedInput, parameterTypes);
+        return new CandidateMethod(m, ScoreHelper.grossScore(compares), 
adaptedInput);
     }
 
     /**
-     * Adjust CandidateMethod considering var args signature. 
+     * Returns the <b>left</b> <code>CandidateMethod</code> if its 
<b>fineScore</b> is greater than the <b>right</b> one,
+     * otherwise returns the <b>right</b> <code>CandidateMethod</code>
+     * @param originalInput
+     * @param left
+     * @param right
+     * @return
      */
-    private void adjustForVariableParameters(CandidateMethod cm, Class<?>[] 
parameterTypes) {
-        if ( parameterTypes.length > 0 && parameterTypes[parameterTypes.length 
- 1].isArray() ) {
-            // then it is a variable parameters function call
-            Object[] newParams = new Object[parameterTypes.length];
-            if ( newParams.length > 1 ) {
-                System.arraycopy( cm.getActualParams(), 0, newParams, 0, 
newParams.length - 1 );
-            }
-            Object[] remaining = new Object[cm.getActualParams().length - 
parameterTypes.length + 1];
-            newParams[newParams.length - 1] = remaining;
-            System.arraycopy( cm.getActualParams(), parameterTypes.length - 1, 
remaining, 0, remaining.length );
-            cm.setActualParams( newParams );
-        }
-    }
-
-    private Object[] calculateActualParams(EvaluationContext ctx, Method m, 
Object[] params, List<String> available) {
-        Annotation[][] pas = m.getParameterAnnotations();
-        List<String> names = new ArrayList<>( m.getParameterCount() );
-        for ( int i = 0; i < m.getParameterCount(); i++ ) {
-            for ( int p = 0; p < pas[i].length; i++ ) {
-                if ( pas[i][p] instanceof ParameterName ) {
-                    names.add( ((ParameterName) pas[i][p]).value() );
-                    break;
-                }
-            }
-            if ( names.get( i ) == null ) {
-                // no name found
-                return null;
-            }
-        }
-        Object[] actualParams = new Object[names.size()];
-        boolean isVariableParameters = m.getParameterCount() > 0 && 
m.getParameterTypes()[m.getParameterCount()-1].isArray();
-        String variableParamPrefix = isVariableParameters ? names.get( 
names.size()-1 ) : null;
-        List<Object> variableParams = isVariableParameters ? new ArrayList<>(  
) : null;
-        for ( Object o : params ) {
-            NamedParameter np = (NamedParameter) o;
-            if( names.contains( np.getName() ) ) {
-                actualParams[names.indexOf( np.getName() )] = np.getValue();
-            } else if( isVariableParameters ) {
-                // check if it is a variable parameters method
-                if( np.getName().matches( variableParamPrefix + "\\d+" ) ) {
-                    int index = Integer.parseInt( np.getName().substring( 
variableParamPrefix.length() ) ) - 1;
-                    if( variableParams.size() <= index ) {
-                        for( int i = variableParams.size(); i < index; i++ ) {
-                            // need to add nulls in case the user skipped 
indexes
-                            variableParams.add( null );
-                        }
-                        variableParams.add( np.getValue() );
-                    } else {
-                        variableParams.set( index, np.getValue() );
-                    }
-                } else {
-                    // invalid parameter, method is incompatible
-                    return null;
-                }
-            } else {
-                // invalid parameter, method is incompatible
-                return null;
-            }
-        }
-        if( isVariableParameters ) {
-            actualParams[ actualParams.length - 1 ] = variableParams.toArray();
-        }
-
-        return actualParams;
+    private CandidateMethod getBestScoredCandidateMethod(Object[] 
originalInput, CandidateMethod left,
+                                                         CandidateMethod 
right) {
+
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, left.getActualParams(),
+                                                                 
left.getParameterTypes());
+        int leftScore = ScoreHelper.fineScore(compares);
+        compares = new ScoreHelper.Compares(originalInput, 
right.getActualParams(), right.getParameterTypes());
+        int rightScore = ScoreHelper.fineScore(compares);
+        return leftScore > rightScore ? left : right;
     }
 
-    private Object normalizeResult(Object result) {
-        // this is to normalize types returned by external functions
-        if (result != null && result.getClass().isArray()) {
-            List<Object> objs = new ArrayList<>();
-            for (int i = 0; i < Array.getLength(result); i++) {
-                objs.add(NumberEvalHelper.coerceNumber(Array.get(result, i)));
-            }
-            return objs;
-        } else {
-            return NumberEvalHelper.coerceNumber(result);
-        }
+    private Object getEitherResult(EvaluationContext ctx, Either<FEELEvent, 
Object> source,
+                                   Supplier<List<String>> 
parameterNamesSupplier,
+                                   Supplier<List<Object>> 
parameterValuesSupplier) {
+        return source.cata((left) -> {
+            ctx.notifyEvt(() -> {
+                              if (left instanceof InvalidParametersEvent 
invalidParametersEvent) {
+                                  
invalidParametersEvent.setNodeName(getName());
+                                  
invalidParametersEvent.setActualParameters(parameterNamesSupplier.get(),
+                                                                             
parameterValuesSupplier.get());
+                              }
+                              return left;
+                          }
+            );
+            return null;
+        }, Function.identity());
     }
 
     protected boolean isCustomFunction() {
         return false;
     }
 
-    private static class CandidateMethod {
-        private Method   apply         = null;
+    protected static class CandidateMethod {
+
+        private Method actualMethod = null;
         private Object[] actualParams;
-        private Class[]  actualClasses = null;
         private int score;
 
-        public CandidateMethod(Object[] actualParams) {
+        public CandidateMethod(Method actualMethod, int score, Object[] 
actualParams) {
+            this.actualMethod = actualMethod;
+            this.score = score;
             this.actualParams = actualParams;
-            populateActualClasses();
-        }
-
-        private void calculateScore() {
-            if ( actualClasses.length > 0 && 
actualClasses[actualClasses.length - 1] != null && 
actualClasses[actualClasses.length - 1].isArray() ) {
-                score = 1;
-            } else {
-                score = 10;
-            }
         }
 
-        public Method getApply() {
-            return apply;
+        public Method getActualMethod() {
+            return actualMethod;
         }
 
-        public void setApply(Method apply) {
-            this.apply = apply;
-            calculateScore();
+        public int getScore() {
+            return score;
         }
 
         public Object[] getActualParams() {
             return actualParams;
         }
 
-        public void setActualParams(Object[] actualParams) {
-            this.actualParams = actualParams;
-            populateActualClasses();
-        }
-
-        private void populateActualClasses() {
-            this.actualClasses = Stream.of( this.actualParams ).map( p -> p != 
null ? p.getClass() : null ).toArray( Class[]::new );
-        }
-
-        public Class[] getActualClasses() {
-            return actualClasses;
+        public Class<?>[] getParameterTypes() {
+            return actualMethod.getParameterTypes();
         }
-
-        public int getScore() {
-            return score;
-        }
-
     }
-
 }
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelper.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelper.java
new file mode 100644
index 0000000000..327f3fb98c
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelper.java
@@ -0,0 +1,318 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.feel.runtime.functions;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.impl.NamedParameter;
+import org.kie.dmn.feel.util.NumberEvalHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.kie.dmn.feel.util.CoerceUtil.coerceParams;
+
+public class BaseFEELFunctionHelper {
+
+    private final static Logger logger = 
LoggerFactory.getLogger(BaseFEELFunctionHelper.class);
+
+    static Object[] getAdjustedParametersForMethod(EvaluationContext ctx, 
Object[] params, boolean isNamedParams,
+                                                   Method m) {
+        logger.trace("getAdjustedParametersForMethod {} {} {} {}", ctx, 
params, isNamedParams, m);
+        Object[] toReturn = addCtxParamIfRequired(ctx, params, isNamedParams, 
m);
+        Class<?>[] parameterTypes = m.getParameterTypes();
+        if (isNamedParams) {
+            // This is inherently frail because it expects that, if, the first 
parameter is NamedParameter and the
+            // function is a CustomFunction, then all parameters are 
NamedParameter
+            NamedParameter[] namedParams =
+                    
Arrays.stream(toReturn).map(NamedParameter.class::cast).toArray(NamedParameter[]::new);
+            toReturn = BaseFEELFunctionHelper.calculateActualParams(m, 
namedParams);
+            if (toReturn == null) {
+                // incompatible method
+                return null;
+            }
+        } else if (toReturn.length > 0) {
+            // if named parameters, then it has been adjusted already in the 
calculateActualParams method,
+            // otherwise adjust here
+            toReturn = adjustForVariableParameters(toReturn, parameterTypes);
+        }
+        toReturn = adjustByCoercion(parameterTypes, toReturn);
+        return toReturn;
+    }
+
+    /**
+     * This method check if the input parameters, set inside the given 
<code>CandidateMethod</code>,
+     * could match the given <code>parameterTypes</code>, eventually 
<b>coerced</b>.
+     * In case of match with coercion, a new <code>Object[]</code> with 
coerced values is returned.
+     * @param parameterTypes
+     * @param actualParams
+     * @return an <code>Object[]</code>, with values eventually coerced, or 
<code>null</code> for incompatible and not coercible values
+     */
+    static Object[] adjustByCoercion(Class<?>[] parameterTypes, Object[] 
actualParams) {
+        logger.trace("adjustByCoercion {} {}", parameterTypes, actualParams);
+        Object[] toReturn = actualParams;
+        int counter = Math.min(parameterTypes.length, actualParams.length);
+        for (int i = 0; i < counter; i++) {
+            Class<?> expectedParameterType = parameterTypes[i];
+            Optional<Object[]> coercedParams;
+            Object actualParam = actualParams[i];
+            if (actualParam != null) {
+                Class<?> currentIdxActualParameterType = 
actualParam.getClass();
+                if 
(expectedParameterType.isAssignableFrom(currentIdxActualParameterType)) {
+                    // not null object assignable to expected type: no need to 
coerce
+                    coercedParams = Optional.of(toReturn);
+                } else {
+                    // attempt to coerce
+                    coercedParams = coerceParams(currentIdxActualParameterType,
+                                                 expectedParameterType, 
toReturn, i);
+                }
+            } else {
+                // null object - no need to coerce
+                coercedParams = Optional.of(toReturn);
+            }
+            if (coercedParams.isPresent()) {
+                toReturn = coercedParams.get();
+                continue;
+            }
+            return null;
+        }
+        return toReturn;
+    }
+
+    /**
+     * This method insert <code>context</code> reference inside the given 
parameters, if the given
+     * <code>Method</code> signature include it.
+     * Depending on the <code>isNamedParams</code>, the reference could be the 
given <code>EvaluationContext</code>
+     * itself, or a <code>NamedParameter</code>
+     * @param ctx
+     * @param params
+     * @param isNamedParams
+     * @param m
+     * @return
+     */
+    static Object[] addCtxParamIfRequired(EvaluationContext ctx, Object[] 
params, boolean isNamedParams, Method m) {
+        logger.trace("addCtxParamIfRequired {} {} {} {}", ctx, params, 
isNamedParams, m);
+        Object[] actualParams;
+        // Here, we check if any of the parameters is an EvaluationContext
+        boolean injectCtx = 
Arrays.stream(m.getParameterTypes()).anyMatch(EvaluationContext.class::isAssignableFrom);
+        if (injectCtx) {
+            actualParams = new Object[params.length + 1];
+            int j = 0;
+            for (int i = 0; i < m.getParameterCount(); i++) {
+                if 
(EvaluationContext.class.isAssignableFrom(m.getParameterTypes()[i])) {
+                    if (isNamedParams) {
+                        actualParams[i] = new NamedParameter("ctx", ctx);
+                    } else {
+                        actualParams[i] = ctx;
+                    }
+                } else if (j < params.length) {
+                    actualParams[i] = params[j];
+                    j++;
+                }
+            }
+        } else {
+            actualParams = params;
+        }
+        return actualParams;
+    }
+
+    /**
+     * Method to retrieve the actual parameters from the given 
<code>NamedParameter[]</code>
+     * It returns <code>null</code> if the actual parameters does not match 
with the <code>Method</code> ones
+     * @param m
+     * @param params
+     * @return an <code>Object[]</code> with mapped values, or 
<code>null</code> if the mapping has not been possible
+     * for all <code>params</code>
+     */
+    static Object[] calculateActualParams(Method m, NamedParameter[] params) {
+        logger.trace("calculateActualParams {} {}", m, params);
+        List<String> names = getParametersNames(m);
+        Object[] actualParams = new Object[names.size()];
+        boolean isVariableParameters =
+                m.getParameterCount() > 0 && 
m.getParameterTypes()[m.getParameterCount() - 1].isArray();
+        String variableParamPrefix = isVariableParameters ? 
names.get(names.size() - 1) : null;
+        List<Object> variableParams = isVariableParameters ? new ArrayList<>() 
: null;
+        for (NamedParameter np : params) {
+            if (!calculateActualParam(np, names, actualParams, 
isVariableParameters, variableParamPrefix,
+                                      variableParams)) {
+                return null;
+            }
+        }
+        if (isVariableParameters) {
+            actualParams[actualParams.length - 1] = variableParams.toArray();
+        }
+        return actualParams;
+    }
+
+    /**
+     * Method to populate the given <code>actualParams</code> or 
<code>variableParams</code> with values extracted
+     * from <code>NamedParameter</code>
+     * @param np
+     * @param names
+     * @param actualParams
+     * @param isVariableParameters
+     * @param variableParamPrefix
+     * @param variableParams
+     * @return <code>true</code> if a mapping has been found, 
<code>false</code> otherwise
+     */
+    static boolean calculateActualParam(NamedParameter np, List<String> names, 
Object[] actualParams,
+                                        boolean isVariableParameters, String 
variableParamPrefix,
+                                        List<Object> variableParams) {
+        logger.trace("calculateActualParam {} {} {} {} {} {}", np, names, 
actualParams, isVariableParameters, variableParamPrefix, variableParams);
+        if (names.contains(np.getName())) {
+            actualParams[names.indexOf(np.getName())] = np.getValue();
+            return true;
+        } else if (isVariableParameters) {
+            return calculateActualParamVariableParameters(np, 
variableParamPrefix, variableParams);
+        } else {
+            // invalid parameter, method is incompatible
+            return false;
+        }
+    }
+
+    /**
+     * Method to populate the given <code>variableParams</code> with values 
extracted from <code>NamedParameter</code>
+     * @param np
+     * @param variableParamPrefix
+     * @param variableParams
+     * @return <code>true</code> if a mapping has been found, 
<code>false</code> otherwise
+     */
+    static boolean calculateActualParamVariableParameters(NamedParameter np, 
String variableParamPrefix,
+                                                          List<Object> 
variableParams) {
+        logger.trace("calculateActualParamVariableParameters {} {} {}", np, 
variableParamPrefix, variableParams);
+        // check if it is a variable parameters method
+        if (np.getName().matches(variableParamPrefix + "\\d+")) {
+            int index = 
Integer.parseInt(np.getName().substring(variableParamPrefix.length())) - 1;
+            if (variableParams.size() <= index) {
+                for (int i = variableParams.size(); i < index; i++) {
+                    // need to add nulls in case the user skipped indexes
+                    variableParams.add(null);
+                }
+                variableParams.add(np.getValue());
+            } else {
+                variableParams.set(index, np.getValue());
+            }
+        } else {
+            // invalid parameter, method is incompatible
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Retrieves the names of the parameters from the given 
<code>Method</code>,
+     * from the ones annotated with <code>ParameterName</code>
+     * @param m
+     * @return
+     */
+    static List<String> getParametersNames(Method m) {
+        logger.trace("getParametersNames {}", m);
+        Annotation[][] pas = m.getParameterAnnotations();
+        List<String> toReturn = new ArrayList<>(m.getParameterCount());
+        for (int i = 0; i < m.getParameterCount(); i++) {
+            for (int p = 0; p < pas[i].length; i++) {
+                if (pas[i][p] instanceof ParameterName) {
+                    toReturn.add(((ParameterName) pas[i][p]).value());
+                    break;
+                }
+            }
+            if (toReturn.get(i) == null) {
+                // no name found
+                return null;
+            }
+        }
+        return toReturn;
+    }
+
+    /**
+     * Method invoked by <code>CustomFunction</code>.
+     * It refactors the input parameters to match the order defined in the 
<code>CustomFunction</code>,
+     * returning the actual value of the given <code>params</code>
+     * @param params
+     * @param pnames the parameters defined in the <code>CustomFunction</code>
+     * @return
+     */
+    static Object[] rearrangeParameters(NamedParameter[] params, List<String> 
pnames) {
+        logger.trace("rearrangeParameters {} {}", params, pnames);
+        if (pnames.isEmpty()) {
+            return params;
+        } else {
+            Object[] actualParams = new Object[pnames.size()];
+            for (int i = 0; i < actualParams.length; i++) {
+                for (int j = 0; j < params.length; j++) {
+                    if (params[j].getName().equals(pnames.get(i))) {
+                        actualParams[i] = params[j].getValue();
+                        break;
+                    }
+                }
+            }
+            return actualParams;
+        }
+    }
+
+    /**
+     * Adjust CandidateMethod considering var args signature.
+     * It converts a series of object to an array, if the last parameter type 
is an array.
+     * It is needed to differentiate function(list) from function(n0...nx), 
e.g.
+     * sum([1,2,3]) = 6
+     * sum(1,2,3) = 6
+     */
+    static Object[] adjustForVariableParameters(Object[] actualParams, 
Class<?>[] parameterTypes) {
+        logger.trace("adjustForVariableParameters {} {}", actualParams, 
parameterTypes);
+        if (parameterTypes.length > 0 && parameterTypes[parameterTypes.length 
- 1].isArray()) {
+            // then it is a variable parameters function call
+            Object[] toReturn = new Object[parameterTypes.length];
+            if (toReturn.length > 1) {
+                System.arraycopy(actualParams, 0, toReturn, 0, toReturn.length 
- 1);
+            }
+            Object[] remaining = new Object[actualParams.length - 
parameterTypes.length + 1];
+            toReturn[toReturn.length - 1] = remaining;
+            System.arraycopy(actualParams, parameterTypes.length - 1, 
remaining, 0, remaining.length);
+            return toReturn;
+        } else {
+            return actualParams;
+        }
+    }
+
+    /**
+     *  This method apply the <code>NumberEvalHelper.coerceNumber</code> to 
the given result or,
+     *  if it is an array, recursively to all its elements
+     * @param result
+     * @return
+     */
+    static Object normalizeResult(Object result) {
+        logger.trace("normalizeResult {}", result);
+        // this is to normalize types returned by external functions
+        if (result != null && result.getClass().isArray()) {
+            List<Object> objs = new ArrayList<>();
+            for (int i = 0; i < Array.getLength(result); i++) {
+                objs.add(NumberEvalHelper.coerceNumber(Array.get(result, i)));
+            }
+            return objs;
+        } else {
+            return NumberEvalHelper.coerceNumber(result);
+        }
+    }
+}
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ScoreHelper.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ScoreHelper.java
new file mode 100644
index 0000000000..1b8b0ac566
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/runtime/functions/ScoreHelper.java
@@ -0,0 +1,237 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.feel.runtime.functions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.ToIntFunction;
+import java.util.stream.Stream;
+
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class used to evaluate <code>Method</code> score based on the given inputs.
+ * It compares the original input with the "adapted" one to match a given 
<code>Method</code>.
+ * For each condition, a value is provided. The top score is obtained when all 
the conditions are met.
+ * <p>
+ * Conditions considered (from most  to less relevant):
+ * Condition                                        Score
+ * 1. last input not array                  ->      100000
+ * 1. last parameter not array              ->      10000
+ * 2. number of parameters                  ->      1000
+ * 3. type identity of all parameters ->    ->      weighted value of matching 
parameters and values 0-1000
+ * 4. coerced to varargs                    ->      -10
+ * 4. null  counts                          ->      null objects * -1
+ */
+public class ScoreHelper {
+
+    private final static Logger logger = 
LoggerFactory.getLogger(ScoreHelper.class);
+
+    private static final List<ToIntFunction<Compares>> GROSS_SCORER_LIST;
+    private static final List<ToIntFunction<Compares>> FINE_SCORER_LIST;
+
+    static int lastInputNotArrayNotArrayScore = 100000;
+    static int lastParameterNotArrayScore = 10000;
+    static int numberOfParametersScore = 1000;
+    static int coercedToVarargsScore = -10;
+
+    static final ToIntFunction<Compares> numberOfParameters = compares -> {
+        int toReturn = compares.originalInput.length == 
compares.parameterTypes.length ? numberOfParametersScore : 0;
+        logger.trace("numberOfParameters {} -> {}", compares, toReturn);
+        return toReturn;
+    };
+
+    static final ToIntFunction<Compares> typeIdentityOfParameters = compares 
-> {
+        int index = Math.min(compares.originalInput.length, 
compares.parameterTypes.length);
+        boolean automaticallyAddedEvaluationContext = 
compares.parameterTypes.length > 0 &&
+                compares.parameterTypes[0] != null &&
+                compares.parameterTypes[0].equals(EvaluationContext.class) &&
+                compares.originalInput.length > 0 &&
+                compares.originalInput[0] != null &&
+                !(compares.originalInput[0] instanceof EvaluationContext);
+        int counter = 0;
+        int matchedEvaluationContext = 0;
+        for (int i = 0; i < index; i++) {
+            if (compares.parameterTypes[i].equals(EvaluationContext.class) &&
+                    compares.originalInput[i] != null &&
+                    compares.originalInput[i] instanceof EvaluationContext) {
+                // Do not consider EvaluationContext for score
+                matchedEvaluationContext += 1;
+                continue;
+            }
+            // In this case, we switch the parameter comparison, ignoring the 
first parameterType
+            int inputIndex = automaticallyAddedEvaluationContext ? i + 1 : i;
+            Class<?> expectedType = compares.parameterTypes[inputIndex];
+            Object originalValue = compares.originalInput[i];
+            Object adaptedValue = compares.adaptedInput != null && 
compares.adaptedInput.length > i ?  compares.adaptedInput[i] : null;
+            if (expectedType.equals(Object.class)) {
+                // parameter type is Object
+                counter += 1;
+            }
+            if (originalValue == null) {
+                // null value has a potential match
+                counter += 1;
+            } else if (!(expectedType.isInstance(originalValue)) && 
!expectedType.isInstance(adaptedValue)) {
+                // do not count it
+                continue;
+            } else if (!(expectedType.equals(Object.class))) {
+                if (adaptedValue != null &&
+                        (expectedType.equals(adaptedValue.getClass()) ||
+                        
expectedType.isAssignableFrom(adaptedValue.getClass())))  {
+                    counter += 2;
+                } else if (expectedType.equals(originalValue.getClass()) ||
+                            
expectedType.isAssignableFrom(originalValue.getClass()))  {
+                    counter += 3;
+                }
+            }
+            logger.trace("typeIdentityOfParameters {} {} -> {}", expectedType, 
originalValue, counter);
+        }
+        int elementsToConsider = index - matchedEvaluationContext;
+        int toReturn = counter > 0 ? Math.round(((float) counter / 
elementsToConsider) * 500) : 0;
+        logger.trace("typeIdentityOfParameters {} -> {}", compares, toReturn);
+        return toReturn;
+    };
+
+    static final ToIntFunction<Compares> lastInputNotArray =
+            compares -> {
+                int toReturn = isLastInputArray(compares.adaptedInput) ? 0 :
+                        lastInputNotArrayNotArrayScore;
+                logger.trace("lastInputNotArray {} -> {}", compares, toReturn);
+                return toReturn;
+            };
+
+    static boolean isLastInputArray(Object[] adaptedInput) {
+        return adaptedInput != null &&
+                adaptedInput.length > 0 &&
+                adaptedInput[adaptedInput.length - 1] != null &&
+                adaptedInput[adaptedInput.length - 1].getClass().isArray();
+    }
+
+    static final ToIntFunction<Compares> lastParameterNotArray =
+            compares -> {
+                int toReturn = isLastParameterArray(compares.parameterTypes) ? 
0 :
+                        lastParameterNotArrayScore;
+                logger.trace("lastParameterNotArray {} -> {}", compares, 
toReturn);
+                return toReturn;
+            };
+
+    static boolean isLastParameterArray(Class<?>[] parameterTypes) {
+        return parameterTypes != null &&
+                parameterTypes.length > 0 &&
+                parameterTypes[parameterTypes.length - 1] != null &&
+                parameterTypes[parameterTypes.length - 1].isArray();
+    }
+
+    static final ToIntFunction<Compares> coercedToVarargs =
+            compares -> {
+                Object[] amendedOriginalInput = compares.originalInput != null 
? Arrays.stream(compares.originalInput)
+                        .filter(o -> !(o instanceof 
EvaluationContext)).toArray() : new Object[0];
+                Object[] amendedAdaptedInput = compares.adaptedInput != null ? 
Arrays.stream(compares.adaptedInput)
+                        .filter(o -> !(o instanceof 
EvaluationContext)).toArray() : new Object[0];
+                int toReturn = 0;
+                if (amendedOriginalInput.length >= amendedAdaptedInput.length 
&&
+                        amendedAdaptedInput.length == 1 &&
+                        
isCoercedToVarargs(amendedOriginalInput[amendedOriginalInput.length - 1],
+                                           amendedAdaptedInput[0])) {
+                    toReturn = coercedToVarargsScore;
+                }
+                logger.trace("coercedToVarargs {} -> {}", compares, toReturn);
+                return toReturn;
+            };
+
+    static boolean isCoercedToVarargs(Object originalInput, Object 
adaptedInput) {
+        boolean isOriginalInputCandidate =
+                originalInput == null || 
!originalInput.getClass().equals(Object.class.arrayType());
+        boolean isAdaptedInputCandidate =
+                adaptedInput != null && 
adaptedInput.getClass().equals(Object.class.arrayType());
+        return isOriginalInputCandidate && isAdaptedInputCandidate;
+    }
+
+    static final ToIntFunction<Compares> nullCounts =
+            compares -> {
+                int toReturn = nullCount(compares.adaptedInput) * -1;
+                logger.trace("nullCounts {} -> {}", compares, toReturn);
+                return toReturn;
+            };
+
+    static {
+        GROSS_SCORER_LIST = new ArrayList<>();
+        GROSS_SCORER_LIST.add(lastInputNotArray);
+        GROSS_SCORER_LIST.add(lastParameterNotArray);
+
+        FINE_SCORER_LIST = new ArrayList<>();
+        FINE_SCORER_LIST.add(numberOfParameters);
+        FINE_SCORER_LIST.add(typeIdentityOfParameters);
+        FINE_SCORER_LIST.add(coercedToVarargs);
+        FINE_SCORER_LIST.add(nullCounts);
+    }
+
+    static int grossScore(Compares toScore) {
+        int toReturn = GROSS_SCORER_LIST.stream()
+                .mapToInt(comparesIntegerFunction ->
+                             comparesIntegerFunction.applyAsInt(toScore))
+                .sum();
+        logger.trace("grossScore {} -> {}", toScore, toReturn);
+        return toReturn;
+    }
+
+    static int fineScore(Compares toScore) {
+        int toReturn = FINE_SCORER_LIST.stream()
+                .mapToInt(comparesIntegerFunction ->
+                             comparesIntegerFunction.applyAsInt(toScore))
+                .sum();
+        logger.trace("fineScore {} -> {}", toScore, toReturn);
+        return toReturn;
+    }
+
+    static int nullCount(Object[] params) {
+        int toReturn = params != null ? (int) 
Stream.of(params).filter(Objects::isNull).count() : 0;
+        logger.trace("nullCount {} -> {}", params, toReturn);
+        return toReturn;
+    }
+
+    private ScoreHelper() {
+    }
+
+    static class Compares {
+
+        private final Object[] originalInput;
+        private final Object[] adaptedInput;
+        private final Class<?>[] parameterTypes;
+
+        public Compares(Object[] originalInput, Object[] adaptedInput, 
Class<?>[] parameterTypes) {
+            this.originalInput = originalInput;
+            this.adaptedInput = adaptedInput;
+            this.parameterTypes = parameterTypes;
+        }
+
+        @Override
+        public String toString() {
+            return "Compares{" +
+                    "originalInput=" + Arrays.toString(originalInput) +
+                    ", adaptedInput=" + Arrays.toString(adaptedInput) +
+                    ", parameterTypes=" + Arrays.toString(parameterTypes) +
+                    '}';
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java
index 421f728249..fa8779609f 100644
--- a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java
+++ b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/util/CoerceUtil.java
@@ -21,6 +21,7 @@ package org.kie.dmn.feel.util;
 import java.time.LocalDate;
 import java.time.ZonedDateTime;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Optional;
 
 import org.kie.dmn.feel.lang.Type;
@@ -49,6 +50,10 @@ public class CoerceUtil {
 
     static Optional<Object> coerceParam(Class<?> 
currentIdxActualParameterType, Class<?> expectedParameterType,
                                         Object actualObject) {
+        /* 10.3.2.9.4 Type conversions
+           from singleton list:
+           When the type of the expression is List<T>, the value of the 
expression is a singleton list and the target
+           type is T, the expression is converted by unwrapping the first 
element. */
         if (Collection.class.isAssignableFrom(currentIdxActualParameterType)) {
             Collection<?> valueCollection = (Collection<?>) actualObject;
             if (valueCollection.size() == 1) {
@@ -62,6 +67,19 @@ public class CoerceUtil {
                 }
             }
         }
+        /* to singleton list:
+           When the type of the expression is T and the target type is List<T> 
the expression is converted to a
+           singleton list. */
+        if (!Collection.class.isAssignableFrom(currentIdxActualParameterType) 
&&
+                Collection.class.isAssignableFrom(expectedParameterType)) {
+            Object singletonValue = coerceParam(currentIdxActualParameterType, 
currentIdxActualParameterType, actualObject)
+                    .orElse(actualObject);
+            return Optional.of(Collections.singletonList(singletonValue));
+        }
+        /* from date to date and time
+            When the type of the expression is date and the target type is 
date and time, the expression is converted
+            to a date time value in which the time of day is UTC midnight 
(00:00:00)
+         */
         if (actualObject instanceof LocalDate localDate &&
                 ZonedDateTime.class.isAssignableFrom(expectedParameterType)) {
             Object coercedObject = 
DateTimeEvalHelper.coerceDateTime(localDate);
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelperTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelperTest.java
new file mode 100644
index 0000000000..39106d29d1
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionHelperTest.java
@@ -0,0 +1,378 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.feel.runtime.functions;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.impl.NamedParameter;
+import org.kie.dmn.feel.runtime.FEELFunction;
+import org.kie.dmn.feel.util.NumberEvalHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BaseFEELFunctionHelperTest {
+
+    private EvaluationContext ctx;
+
+    @BeforeEach
+    public void setUp() {
+        ctx = CodegenTestUtil.newEmptyEvaluationContext();
+    }
+
+    @Test
+    void getAdjustedParametersForMethod() throws NoSuchMethodException, 
InvocationTargetException, IllegalAccessException {
+        // StddevFunction.invoke(@ParameterName( "list" ) List<?> list)
+        Method method = StddevFunction.class.getMethod("invoke", List.class);
+        assertNotNull(method);
+        Object actualValue = Arrays.asList(2, 4, 7, 5);
+        Object[] parameters = {new NamedParameter("list", actualValue)};
+
+        Object[] retrieved = 
BaseFEELFunctionHelper.getAdjustedParametersForMethod(ctx, parameters, true, 
method);
+        assertNotNull(retrieved);
+        assertEquals(parameters.length, retrieved.length);
+        assertEquals(actualValue, retrieved[0]);
+    }
+
+    @Test
+    void adjustByCoercion() {
+        // no coercion needed
+        Object actualParam = List.of(true, false);
+        Class<?>[] parameterTypes = new Class[]{List.class};
+        Object[] actualParams = {actualParam};
+        Object[] retrieved = 
BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, actualParams);
+        assertEquals(actualParams, retrieved);
+
+        actualParam = "StringA";
+        parameterTypes = new Class[]{String.class};
+        actualParams = new Object[]{actualParam};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertEquals(actualParams, retrieved);
+
+        // coercing more objects to different types: fails
+        parameterTypes = new Class[]{String.class, Integer.class};
+        actualParams = new Object[]{"String", 34 };
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertEquals(actualParams, retrieved);
+
+        // not coercing null value to not-list type
+        actualParam = null;
+        actualParams = new Object[]{actualParam};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertEquals(actualParams, retrieved);
+
+        // not coercing null value to singleton list
+        parameterTypes = new Class[]{List.class};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertEquals(actualParams, retrieved);
+
+        // coercing not-null value to singleton list
+        actualParam = "StringA";
+        actualParams = new Object[]{actualParam};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertNotNull(retrieved);
+        assertNotEquals(actualParams, retrieved);
+        assertEquals(1, retrieved.length);
+        assertNotNull(retrieved[0]);
+        assertThat(retrieved[0]).isInstanceOf(List.class);
+        List retrievedList = (List) retrieved[0];
+        assertEquals(1, retrievedList.size());
+        assertEquals(actualParam, retrievedList.get(0));
+
+        // coercing null value to array: fails
+        parameterTypes = new Class[]{Object.class.arrayType()};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertNull(retrieved);
+
+        // coercing one object to different type: fails
+        actualParam = 45;
+        parameterTypes = new Class[]{String.class};
+        actualParams = new Object[]{actualParam};
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertNull(retrieved);
+
+        // coercing more objects to different types: fails
+        parameterTypes = new Class[]{String.class, Integer.class};
+        actualParams = new Object[]{"String", "34" };
+        retrieved = BaseFEELFunctionHelper.adjustByCoercion(parameterTypes, 
actualParams);
+        assertNull(retrieved);
+
+    }
+
+    @Test
+    void addCtxParamIfRequired() throws NoSuchMethodException {
+        // AllFunction.invoke(@ParameterName( "list" ) List list)
+        Method method = AllFunction.class.getMethod("invoke", List.class);
+        assertNotNull(method);
+        Object[] parameters = {List.of(true, false)};
+
+        Object[] retrieved = BaseFEELFunctionHelper.addCtxParamIfRequired(ctx, 
parameters, true, method);
+        assertNotNull(retrieved);
+        assertEquals(parameters.length, retrieved.length);
+        for (int i = 0; i < parameters.length; i++) {
+            assertEquals(parameters[i], retrieved[i]);
+        }
+
+        // SortFunction.invoke(@ParameterName( "ctx" ) EvaluationContext ctx,
+        //                                             @ParameterName("list") 
List list,
+        //                                             
@ParameterName("precedes") FEELFunction function)
+        method = SortFunction.class.getMethod("invoke", 
EvaluationContext.class, List.class, FEELFunction.class);
+        assertNotNull(method);
+        parameters = new Object[]{List.of(1, 2), AllFunction.INSTANCE};
+        // direct reference to ctx
+        retrieved = BaseFEELFunctionHelper.addCtxParamIfRequired(ctx, 
parameters, false, method);
+        assertNotNull(retrieved);
+        assertEquals(parameters.length + 1, retrieved.length);
+        assertEquals(ctx, retrieved[0]);
+        for (int i = 0; i < parameters.length; i++) {
+            assertEquals(parameters[i], retrieved[i + 1]);
+        }
+
+        // NamedParameter reference to ctx
+        retrieved = BaseFEELFunctionHelper.addCtxParamIfRequired(ctx, 
parameters, true, method);
+        assertNotNull(retrieved);
+        assertEquals(parameters.length + 1, retrieved.length);
+        assertEquals(NamedParameter.class, retrieved[0].getClass());
+        NamedParameter retrievedNamedParameter = (NamedParameter) retrieved[0];
+        assertEquals("ctx", retrievedNamedParameter.getName());
+        assertEquals(ctx, retrievedNamedParameter.getValue());
+        for (int i = 0; i < parameters.length; i++) {
+            assertEquals(parameters[i], retrieved[i + 1]);
+        }
+    }
+
+    @Test
+    void calculateActualParams() throws NoSuchMethodException {
+        // CeilingFunction.invoke(@ParameterName( "n" ) BigDecimal n)
+        Method m = CeilingFunction.class.getMethod("invoke", BigDecimal.class);
+        assertNotNull(m);
+        NamedParameter[] parameters = {new NamedParameter("n", 
BigDecimal.valueOf(1.5))};
+        Object[] retrieved = BaseFEELFunctionHelper.calculateActualParams(m, 
parameters);
+        assertNotNull(retrieved);
+        assertEquals(parameters.length, retrieved.length);
+        assertEquals(parameters[0].getValue(), retrieved[0]);
+
+        parameters = new NamedParameter[]{new NamedParameter("undefined", 
BigDecimal.class)};
+        retrieved = BaseFEELFunctionHelper.calculateActualParams(m, 
parameters);
+        assertNull(retrieved);
+    }
+
+    @Test
+    void calculateActualParam() {
+        // populate by NamedParameter value
+        NamedParameter np = new NamedParameter("n", BigDecimal.valueOf(1.5));
+        List<String> names = Collections.singletonList("n");
+        Object[] actualParams = new Object[1];
+        boolean isVariableParameters = false;
+        String variableParamPrefix = null;
+        List<Object> variableParams = null;
+        assertTrue(BaseFEELFunctionHelper.calculateActualParam(np, names, 
actualParams, isVariableParameters,
+                                                               
variableParamPrefix, variableParams));
+        assertEquals(np.getValue(), actualParams[0]);
+
+        np = new NamedParameter("undefined", BigDecimal.valueOf(1.5));
+        actualParams = new Object[1];
+        assertFalse(BaseFEELFunctionHelper.calculateActualParam(np, names, 
actualParams, isVariableParameters,
+                                                                
variableParamPrefix, variableParams));
+
+        // populate by variableparameters
+        variableParamPrefix = "varPref";
+        int varIndex = 12;
+        np = new NamedParameter(variableParamPrefix + varIndex, 
BigDecimal.valueOf(1.5));
+        names = Collections.singletonList("n");
+        actualParams = new Object[1];
+        isVariableParameters = true;
+        variableParams = new ArrayList();
+        assertTrue(BaseFEELFunctionHelper.calculateActualParam(np, names, 
actualParams, isVariableParameters,
+                                                               
variableParamPrefix, variableParams));
+        assertEquals(varIndex, variableParams.size());
+        for (int i = 0; i < varIndex - 1; i++) {
+            assertNull(variableParams.get(i));
+        }
+        assertEquals(np.getValue(), variableParams.get(varIndex - 1));
+    }
+
+    @Test
+    void calculateActualParamVariableParameters() {
+        // populate by variableparameters
+        String variableParamPrefix = "varPref";
+        int varIndex = 12;
+        NamedParameter np = new NamedParameter(variableParamPrefix + varIndex, 
BigDecimal.valueOf(1.5));
+        List<Object> variableParams = new ArrayList<>();
+        
assertTrue(BaseFEELFunctionHelper.calculateActualParamVariableParameters(np, 
variableParamPrefix,
+                                                                               
  variableParams));
+        assertEquals(varIndex, variableParams.size());
+        for (int i = 0; i < varIndex - 1; i++) {
+            assertNull(variableParams.get(i));
+        }
+        assertEquals(np.getValue(), variableParams.get(varIndex - 1));
+
+        np = new NamedParameter("variableParamPrefix", 
BigDecimal.valueOf(1.5));
+        variableParams = new ArrayList<>();
+        
assertFalse(BaseFEELFunctionHelper.calculateActualParamVariableParameters(np, 
variableParamPrefix,
+                                                                               
   variableParams));
+    }
+
+    @Test
+    void getParametersNames() throws NoSuchMethodException {
+        // SumFunction.invoke(@ParameterName("n") Object[] list)
+        Method m = SumFunction.class.getMethod("invoke", 
Object.class.arrayType());
+        assertNotNull(m);
+        List<String> retrieved = BaseFEELFunctionHelper.getParametersNames(m);
+        assertNotNull(retrieved);
+        int counter = 0;
+        Annotation[][] pas = m.getParameterAnnotations();
+        for (Annotation[] annotations : pas) {
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof ParameterName parameterName) {
+                    assertEquals(parameterName.value(), 
retrieved.get(counter));
+                    counter++;
+                }
+            }
+        }
+
+        // DateAndTimeFunction.invoke(@ParameterName( "year" ) Number year, 
@ParameterName( "month" ) Number month,
+        // @ParameterName( "day" ) Number day,
+        //                                                 @ParameterName( 
"hour" ) Number hour, @ParameterName(
+        //                                                 "minute" ) Number 
minute, @ParameterName( "second" )
+        //                                                 Number second,
+        //                                                 @ParameterName( 
"hour offset" ) Number hourOffset )
+        m = DateAndTimeFunction.class.getMethod("invoke", Number.class,
+                                                Number.class,
+                                                Number.class,
+                                                Number.class,
+                                                Number.class,
+                                                Number.class,
+                                                Number.class);
+        assertNotNull(m);
+        retrieved = BaseFEELFunctionHelper.getParametersNames(m);
+        assertNotNull(retrieved);
+        counter = 0;
+        pas = m.getParameterAnnotations();
+        for (Annotation[] annotations : pas) {
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof ParameterName parameterName) {
+                    assertEquals(parameterName.value(), 
retrieved.get(counter));
+                    counter++;
+                }
+            }
+        }
+    }
+
+    @Test
+    void rearrangeParameters() {
+        NamedParameter[] params = {new NamedParameter("fake", new Object())};
+        Object[] retrieved = 
BaseFEELFunctionHelper.rearrangeParameters(params, Collections.emptyList());
+        assertNotNull(retrieved);
+        assertEquals(params, retrieved);
+
+        List<String> pnames = IntStream.range(0, 3)
+                .mapToObj(i -> "Parameter_" + i)
+                .toList();
+
+        // single param in correct position
+        params = new NamedParameter[]{new NamedParameter(pnames.get(0), new 
Object())};
+        retrieved = BaseFEELFunctionHelper.rearrangeParameters(params, pnames);
+        assertNotNull(retrieved);
+        assertEquals(pnames.size(), retrieved.length);
+        for (int i = 0; i < retrieved.length; i++) {
+            if (i == 0) {
+                assertEquals(params[0].getValue(), retrieved[i]);
+            } else {
+                assertNull(retrieved[i]);
+            }
+        }
+
+        // single param in wrong position
+        params = new NamedParameter[]{new NamedParameter(pnames.get(2), new 
Object())};
+        retrieved = BaseFEELFunctionHelper.rearrangeParameters(params, pnames);
+        assertNotNull(retrieved);
+        assertEquals(pnames.size(), retrieved.length);
+        for (int i = 0; i < retrieved.length; i++) {
+            if (i == 2) {
+                assertEquals(params[0].getValue(), retrieved[i]);
+            } else {
+                assertNull(retrieved[i]);
+            }
+        }
+
+        // reverting the whole order
+        params = new NamedParameter[]{new NamedParameter(pnames.get(2), new 
Object()),
+                new NamedParameter(pnames.get(1), new Object()),
+                new NamedParameter(pnames.get(0), new Object())};
+        retrieved = BaseFEELFunctionHelper.rearrangeParameters(params, pnames);
+        assertNotNull(retrieved);
+        assertEquals(pnames.size(), retrieved.length);
+        for (int i = 0; i < retrieved.length; i++) {
+            switch (i) {
+                case 0:
+                    assertEquals(params[2].getValue(), retrieved[i]);
+                    break;
+                case 1:
+                    assertEquals(params[1].getValue(), retrieved[i]);
+                    break;
+                case 2:
+                    assertEquals(params[0].getValue(), retrieved[i]);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    void normalizeResult() {
+        List<Object> originalResult = List.of(3, "4", 56);
+        Object result = originalResult.toArray();
+        Object retrieved = BaseFEELFunctionHelper.normalizeResult(result);
+        assertNotNull(retrieved);
+        assertInstanceOf(List.class, retrieved);
+        List<Object> retrievedList = (List<Object>) retrieved;
+        assertEquals(originalResult.size(), retrievedList.size());
+        for (int i = 0; i < originalResult.size(); i++) {
+            assertEquals(NumberEvalHelper.coerceNumber(originalResult.get(i)), 
retrievedList.get(i));
+        }
+
+        result = 23;
+        retrieved = BaseFEELFunctionHelper.normalizeResult(result);
+        assertNotNull(retrieved);
+        assertEquals(NumberEvalHelper.coerceNumber(result), retrieved);
+
+        result = "23";
+        retrieved = BaseFEELFunctionHelper.normalizeResult(result);
+        assertNotNull(retrieved);
+        assertEquals(NumberEvalHelper.coerceNumber(result), retrieved);
+    }
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionTest.java
new file mode 100644
index 0000000000..dde34f0d62
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/BaseFEELFunctionTest.java
@@ -0,0 +1,328 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.feel.runtime.functions;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.temporal.TemporalAccessor;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.ast.BaseNode;
+import org.kie.dmn.feel.lang.ast.InfixOpNode;
+import org.kie.dmn.feel.lang.ast.InfixOperator;
+import org.kie.dmn.feel.lang.ast.NameRefNode;
+import org.kie.dmn.feel.lang.ast.NullNode;
+import org.kie.dmn.feel.lang.ast.NumberNode;
+import org.kie.dmn.feel.lang.impl.NamedParameter;
+import org.kie.dmn.feel.lang.types.BuiltInType;
+import org.kie.dmn.feel.runtime.FEELFunction;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BaseFEELFunctionTest {
+
+    private EvaluationContext ctx;
+
+    @BeforeEach
+    public void setUp() {
+        ctx = CodegenTestUtil.newEmptyEvaluationContext();
+    }
+
+    @Test
+    void invokeReflectiveCustomFunction() {
+        List<FEELFunction.Param> parameters = List.of(new 
FEELFunction.Param("foo", BuiltInType.UNKNOWN),
+                                                  new 
FEELFunction.Param("person's age", BuiltInType.UNKNOWN));
+
+        BaseNode left = new InfixOpNode(InfixOperator.EQ,
+                                        new NameRefNode(BuiltInType.UNKNOWN, 
"foo"),
+                                        new NullNode(""),
+                                        "foo = null");
+        BaseNode right = new InfixOpNode(InfixOperator.LT,
+                                        new NameRefNode(BuiltInType.UNKNOWN, 
"person's age"),
+                                        new NumberNode(BigDecimal.valueOf(18), 
"18"),
+                                        "person's age < 18");
+        BaseNode body = new InfixOpNode(InfixOperator.AND, left, right, "foo = 
null and person's age < 18");
+        BaseFEELFunction toTest = new CustomFEELFunction("<anonymous>",
+                                                         parameters,
+                                                         body,
+                                                         ctx);
+       Object[] params = {new NamedParameter("foo", null),
+       new NamedParameter("person's age", 16)};
+        Object retrieved = toTest.invokeReflectively(ctx, params);
+        assertNotNull(retrieved);
+        assertInstanceOf(Boolean.class, retrieved);
+        assertTrue((Boolean) retrieved);
+
+        params = new Object[]{new NamedParameter("foo", null),
+                new NamedParameter("person's age", 19)};
+        retrieved = toTest.invokeReflectively(ctx, params);
+        assertNotNull(retrieved);
+        assertInstanceOf(Boolean.class, retrieved);
+        assertFalse((Boolean) retrieved);
+    }
+
+    @Test
+    void getAllFunctionCandidateMethod() {
+        BaseFEELFunction toTest = AllFunction.INSTANCE;
+
+        // invoke(@ParameterName( "list" ) List list)
+        Object[] parameters = {List.of(true, false)};
+        BaseFEELFunction.CandidateMethod candidateMethodRetrieved = 
toTest.getCandidateMethod(ctx, parameters, false);
+        assertNotNull(candidateMethodRetrieved);
+        Method retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        Parameter[] parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(List.class, parametersRetrieved[0].getType());
+
+        // invoke(@ParameterName( "b" ) Object[] list)
+        parameters = new Object[]{true, false};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(Object.class.arrayType(), 
parametersRetrieved[0].getType());
+    }
+
+    @Test
+    void getDateAndTimeFunctionCandidateMethod() {
+        BaseFEELFunction toTest = DateAndTimeFunction.INSTANCE;
+
+        // invoke(@ParameterName( "from" ) String val)
+        Object[] parameters = {"2017-09-07T10:20:30"};
+        BaseFEELFunction.CandidateMethod candidateMethodRetrieved = 
toTest.getCandidateMethod(ctx, parameters, false);
+        assertNotNull(candidateMethodRetrieved);
+        Method retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        Parameter[] parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(String.class, parametersRetrieved[0].getType());
+
+        // invoke(@ParameterName( "date" ) TemporalAccessor date, 
@ParameterName( "time" ) TemporalAccessor time)
+        parameters = new Object[]{LocalDate.of(2017, 6, 12), LocalTime.of(10, 
6, 20)};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(2, parametersRetrieved.length);
+        Arrays.stream(parametersRetrieved).forEach(parameter -> 
assertEquals(TemporalAccessor.class,
+                                                                             
parameter.getType()));
+
+//        invoke(@ParameterName( "year" ) Number year, @ParameterName( "month" 
) Number month, @ParameterName( "day"
+//        ) Number day,
+//               @ParameterName( "hour" ) Number hour, @ParameterName( 
"minute" ) Number minute, @ParameterName(
+//               "second" ) Number second )
+        parameters = new Object[]{2017, 6, 12, 10, 6, 20};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(6, parametersRetrieved.length);
+        Arrays.stream(parametersRetrieved).forEach(parameter -> 
assertEquals(Number.class, parameter.getType()));
+
+//        invoke(@ParameterName( "year" ) Number year, @ParameterName( "month" 
) Number month, @ParameterName( "day"
+//        ) Number day,
+//               @ParameterName( "hour" ) Number hour, @ParameterName( 
"minute" ) Number minute, @ParameterName(
+//               "second" ) Number second,
+//               @ParameterName( "hour offset" ) Number hourOffset )
+        parameters = new Object[]{2017, 6, 12, 10, 6, 20, 2};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(7, parametersRetrieved.length);
+        Arrays.stream(parametersRetrieved).forEach(parameter -> 
assertEquals(Number.class, parameter.getType()));
+
+//        invoke(@ParameterName( "year" ) Number year, @ParameterName( "month" 
) Number month, @ParameterName( "day"
+//        ) Number day,
+//               @ParameterName( "hour" ) Number hour, @ParameterName( 
"minute" ) Number minute, @ParameterName(
+//               "second" ) Number second,
+//               @ParameterName( "timezone" ) String timezone )
+        parameters = new Object[]{2017, 6, 12, 10, 6, 20, "Europe/Paris"};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(7, parametersRetrieved.length);
+        for (int i = 0; i < 6; i++) {
+            assertEquals(Number.class, parametersRetrieved[i].getType());
+        }
+        assertEquals(String.class, parametersRetrieved[6].getType());
+    }
+
+    @Test
+    void getExtendedTimeFunctionCandidateMethod() {
+        BaseFEELFunction toTest = 
org.kie.dmn.feel.runtime.functions.extended.TimeFunction.INSTANCE;
+
+        // invoke(@ParameterName( "from" ) String val)
+        Object[] parameters = {"10:20:30"};
+        BaseFEELFunction.CandidateMethod candidateMethodRetrieved = 
toTest.getCandidateMethod(ctx, parameters, false);
+        assertNotNull(candidateMethodRetrieved);
+        Method retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        Parameter[] parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(String.class, parametersRetrieved[0].getType());
+
+//      invoke(
+//            @ParameterName("hour") Number hour, @ParameterName("minute") 
Number minute,
+//            @ParameterName("second") Number seconds)
+        parameters = new Object[]{10, 6, 20};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(3, parametersRetrieved.length);
+        Arrays.stream(parametersRetrieved).forEach(parameter -> 
assertEquals(Number.class, parameter.getType()));
+
+//     invoke(
+//            @ParameterName("hour") Number hour, @ParameterName("minute") 
Number minute,
+//            @ParameterName("second") Number seconds, 
@ParameterName("offset") Duration offset)
+        parameters = new Object[]{10, 6, 20, Duration.ofHours(3)};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(4, parametersRetrieved.length);
+        for (int i = 0; i < 3; i++) {
+            assertEquals(Number.class, parametersRetrieved[i].getType());
+        }
+        assertEquals(Duration.class, parametersRetrieved[3].getType());
+
+//      invoke(@ParameterName("from") TemporalAccessor date
+        parameters = new Object[]{LocalTime.of(10, 6, 20)};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(TemporalAccessor.class, parametersRetrieved[0].getType());
+    }
+
+    @Test
+    void getSortFunctionCandidateMethod() {
+        BaseFEELFunction toTest = SortFunction.INSTANCE;
+
+        // invoke(@ParameterName( "ctx" ) EvaluationContext ctx,
+        //                                             @ParameterName("list") 
List list,
+        //                                             
@ParameterName("precedes") FEELFunction function
+        Object[] parameters = {List.of(1, 2), AllFunction.INSTANCE};
+        BaseFEELFunction.CandidateMethod candidateMethodRetrieved = 
toTest.getCandidateMethod(ctx, parameters, false);
+        assertNotNull(candidateMethodRetrieved);
+        Method retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        Parameter[] parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(3, parametersRetrieved.length);
+        assertEquals(EvaluationContext.class, 
parametersRetrieved[0].getType());
+        assertEquals(List.class, parametersRetrieved[1].getType());
+        assertEquals(FEELFunction.class, parametersRetrieved[2].getType());
+
+        // invoke(@ParameterName("list") List list)
+        parameters = new Object[]{List.of(1, 3, 5)};
+        candidateMethodRetrieved = toTest.getCandidateMethod(ctx, parameters, 
false);
+        assertNotNull(candidateMethodRetrieved);
+        retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(List.class, parametersRetrieved[0].getType());
+    }
+
+    @Test
+    void getStddevFunctionCandidateMethod() {
+        BaseFEELFunction toTest = StddevFunction.INSTANCE;
+
+        // invoke(@ParameterName("list") List<?> list)
+        Object actualValue = Arrays.asList(2, 4, 7, 5);
+        Object[] parameters = {new NamedParameter("list", actualValue)};
+        BaseFEELFunction.CandidateMethod candidateMethodRetrieved = 
toTest.getCandidateMethod(ctx, parameters, false);
+        assertNotNull(candidateMethodRetrieved);
+        Method retrieved = candidateMethodRetrieved.getActualMethod();
+        assertNotNull(retrieved);
+        assertTrue(Modifier.isPublic(retrieved.getModifiers()));
+        assertEquals("invoke", retrieved.getName());
+        Parameter[] parametersRetrieved = retrieved.getParameters();
+        assertNotNull(parametersRetrieved);
+        assertEquals(1, parametersRetrieved.length);
+        assertEquals(List.class, parametersRetrieved[0].getType());
+    }
+
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ScorerHelperTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ScorerHelperTest.java
new file mode 100644
index 0000000000..396e0a0356
--- /dev/null
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/runtime/functions/ScorerHelperTest.java
@@ -0,0 +1,610 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.kie.dmn.feel.runtime.functions;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.jupiter.api.Test;
+import org.kie.dmn.feel.codegen.feel11.CodegenTestUtil;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.impl.NamedParameter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static 
org.kie.dmn.feel.runtime.functions.ScoreHelper.coercedToVarargsScore;
+import static 
org.kie.dmn.feel.runtime.functions.ScoreHelper.lastInputNotArrayNotArrayScore;
+import static 
org.kie.dmn.feel.runtime.functions.ScoreHelper.lastParameterNotArrayScore;
+import static 
org.kie.dmn.feel.runtime.functions.ScoreHelper.numberOfParametersScore;
+
+class ScorerHelperTest {
+
+    @Test
+    void grossScore() {
+        Object[] originalInput = new Object[] { "String" };
+        Class<?>[] parameterTypes = new Class<?>[] { String.class };
+        Object[] adaptedInput = new Object[] { "String" };
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, adaptedInput, parameterTypes);
+        int retrieved = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        int expected = lastInputNotArrayNotArrayScore + 
lastParameterNotArrayScore;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        adaptedInput = new Object[] { "String", null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        adaptedInput = null;
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB" };
+        parameterTypes = new Class<?>[] { String.class, String.class };
+        adaptedInput = new Object[] { "StringA", "StringB" };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB" };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { new Object[] {"StringA", "StringB"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //      0 (lastParameterNotArray)
+        //      0 (lastInputNotArray)
+        expected = 0;
+        assertEquals( expected, retrieved);
+
+
+        originalInput = new Object[] { "StringA" };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { new Object[] {"StringA"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //      0 (lastParameterNotArray)
+        //      0 (lastInputNotArray)
+        expected = 0;
+        assertEquals( expected, retrieved);
+
+        originalInput = new Object[] { "StringA" };
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { List.of("StringA") };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        int retrievedToCompare = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals( expected, retrievedToCompare);
+        assertThat(retrievedToCompare).isGreaterThan(retrieved);
+
+
+        originalInput = new Object[] { null };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //      0 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore;
+        assertEquals( expected, retrieved);
+
+        originalInput = new Object[] { null };
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrievedToCompare = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals( expected, retrievedToCompare);
+        assertThat(retrievedToCompare).isGreaterThan(retrieved);
+
+        Object actualValue = Arrays.asList(2, 4, 7, 5);
+        originalInput = new Object[] {new NamedParameter("list", actualValue)};
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { actualValue };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals( expected, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrievedToCompare = ScoreHelper.grossScore(compares);
+        //  10000 (lastParameterNotArray)
+        // 100000 (lastInputNotArray)
+        expected = lastInputNotArrayNotArrayScore + lastParameterNotArrayScore;
+        assertEquals( expected, retrievedToCompare);
+    }
+
+    @Test
+    void fineScore() {
+        Object[] originalInput = new Object[] { "String" };
+        Class<?>[] parameterTypes = new Class<?>[] { String.class };
+        Object[] adaptedInput = new Object[] { "String" };
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, adaptedInput, parameterTypes);
+        int retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        int expected = numberOfParametersScore + 1000;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        adaptedInput = new Object[] { "String", null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //     -1 (nullCounts)
+        //      0 (coercedToVarargs)
+        //    500 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 500 -1;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        adaptedInput = null;
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //    750 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 750;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB" };
+        parameterTypes = new Class<?>[] { String.class, String.class };
+        adaptedInput = new Object[] { "StringA", "StringB" };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 1000;
+        assertEquals(expected, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB" };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { new Object[] {"StringA", "StringB"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //    -10 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //      0 (numberOfParameters)
+        expected = coercedToVarargsScore + 1000;
+        assertEquals( expected, retrieved);
+
+
+        originalInput = new Object[] { "StringA" };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { new Object[] {"StringA"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //    -10 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + coercedToVarargsScore + 1000;
+        assertEquals( expected, retrieved);
+
+        originalInput = new Object[] { "StringA" };
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { List.of("StringA") };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        int retrievedToCompare = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 1000;
+        assertEquals( expected, retrievedToCompare);
+        assertThat(retrievedToCompare).isGreaterThan(retrieved);
+
+
+        originalInput = new Object[] { null };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //     -1 (nullCounts)
+        //      0 (coercedToVarargs)
+        //    500 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 500 -1;
+        assertEquals( expected, retrieved);
+
+        originalInput = new Object[] { null };
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //     -1 (nullCounts)
+        //      0 (coercedToVarargs)
+        //    500 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 500 -1;
+        assertEquals( expected, retrieved);
+
+        Object actualValue = Arrays.asList(2, 4, 7, 5);
+        originalInput = new Object[] {new NamedParameter("list", actualValue)};
+        parameterTypes = new Class<?>[] { List.class };
+        adaptedInput = new Object[] { actualValue };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //   1000 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 1000;
+        assertEquals( expected, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.fineScore(compares);
+        //      0 (nullCounts)
+        //      0 (coercedToVarargs)
+        //    500 (typeIdentityOfParameters)
+        //   1000 (numberOfParameters)
+        expected = numberOfParametersScore + 500;
+        assertEquals( expected, retrieved);
+    }
+
+    @Test
+    void lastInputNotArray(){
+        Object[] adaptedInput = new Object[] { "String" };
+        ScoreHelper.Compares compares = new ScoreHelper.Compares(null, 
adaptedInput, null);
+        int retrieved = ScoreHelper.lastInputNotArray.applyAsInt(compares);
+        assertEquals(lastInputNotArrayNotArrayScore, retrieved);
+
+        adaptedInput = new Object[] { "String", 34, new Object() };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.lastInputNotArray.applyAsInt(compares);
+        assertEquals(lastInputNotArrayNotArrayScore, retrieved);
+
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.lastInputNotArray.applyAsInt(compares);
+        assertEquals(lastInputNotArrayNotArrayScore, retrieved);
+
+        adaptedInput = new Object[] { new Object[]{} };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.lastInputNotArray.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        adaptedInput = new Object[] { "String", 34,  new Object[]{} };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.lastInputNotArray.applyAsInt(compares);
+        assertEquals(0, retrieved);
+    }
+
+    @Test
+    void lastParameterNotArray(){
+        Class<?>[] parameterTypes = new Class<?>[] { String.class };
+        ScoreHelper.Compares compares = new ScoreHelper.Compares(null, null, 
parameterTypes);
+        int retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { String.class, Object.class };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { null };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { String.class, null };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class.arrayType(), 
String.class };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class.arrayType(), null };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(lastParameterNotArrayScore, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        parameterTypes = new Class<?>[] { String.class, 
Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        parameterTypes = new Class<?>[] { null, Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(null, null, parameterTypes);
+        retrieved = ScoreHelper.lastParameterNotArray.applyAsInt(compares);
+        assertEquals(0, retrieved);
+    }
+
+    @Test
+    void numberOfParameters() {
+        Object[] originalInput = new Object[] { "String" };
+        Class<?>[] parameterTypes = new Class<?>[] { String.class };
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, null, parameterTypes);
+        int retrieved = ScoreHelper.numberOfParameters.applyAsInt(compares);
+        assertEquals(numberOfParametersScore, retrieved);
+
+        originalInput = new Object[] { "String" };
+        parameterTypes = new Class<?>[] { Object.class };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.numberOfParameters.applyAsInt(compares);
+        assertEquals(numberOfParametersScore, retrieved);
+
+        originalInput = new Object[] { new Object[]{ "String", 34 } };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.numberOfParameters.applyAsInt(compares);
+        assertEquals(numberOfParametersScore, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        parameterTypes = new Class<?>[] { Object.class };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.numberOfParameters.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.numberOfParameters.applyAsInt(compares);
+        assertEquals(0, retrieved);
+    }
+
+    @Test
+    void typeIdentityOfParameters() {
+        Object[] originalInput = new Object[] { "String" };
+        Object[] adaptedInput = originalInput;
+        Class<?>[] parameterTypes = new Class<?>[] { String.class };
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, adaptedInput, parameterTypes);
+        int retrieved = 
ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1000, retrieved);
+
+        parameterTypes = new Class<?>[] { Object.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(500, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        adaptedInput = originalInput;
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1000, retrieved);
+
+        parameterTypes = new Class<?>[] { String.class, Object.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(750, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        adaptedInput = null;
+        parameterTypes = new Class<?>[] { String.class, Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(750, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB", 40 };
+        parameterTypes = new Class<?>[] { String.class, Integer.class, 
Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1000, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB", 40 };
+        parameterTypes = new Class<?>[] { String.class, Integer.class, 
String.class };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(500, retrieved);
+
+        originalInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String" };
+        adaptedInput = originalInput;
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
String.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1000, retrieved);
+
+        originalInput = new Object[] { "String" };
+        adaptedInput =  new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String" };
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
String.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1500, retrieved);
+
+        originalInput = new Object[] { "String" };
+        adaptedInput = null;
+        parameterTypes = new Class<?>[] {  Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        adaptedInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String", 34 };
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
String.class, Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1500, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        adaptedInput = originalInput;
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
String.class, Object.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(750, retrieved);
+
+        originalInput = new Object[] { "String", "34" };
+        adaptedInput = null;
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
String.class, Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(750, retrieved);
+
+        originalInput = new Object[] { "String" };
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String" };
+        parameterTypes = new Class<?>[] { EvaluationContext.class, 
Integer.class };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] { null };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        compares = new ScoreHelper.Compares(originalInput, null, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(500, retrieved);
+
+        originalInput = new Object[] { "StringA", "StringB" };
+        parameterTypes = new Class<?>[] { Object.class.arrayType() };
+        adaptedInput = new Object[] { new Object[] {"StringA", "StringB"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, 
parameterTypes);
+        retrieved = ScoreHelper.typeIdentityOfParameters.applyAsInt(compares);
+        assertEquals(1000, retrieved);
+    }
+
+    @Test
+    void coercedToVarargs() {
+        Object[] originalInput = new Object[] { "String" };
+        Object[] adaptedInput = new Object[] { "String" };
+        ScoreHelper.Compares compares = new 
ScoreHelper.Compares(originalInput, adaptedInput, null);
+        int retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] { "String" };
+        adaptedInput = new Object[] { new Object[] {"String"} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        adaptedInput = new Object[] { new Object[] {"String", 34} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+
+        originalInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String", 34 };
+        adaptedInput = new Object[] { new Object[] {"String", 34} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+
+        originalInput = new Object[] { "String", 34 };
+        adaptedInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), new Object[] {"String", 34} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+
+        originalInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), "String", 34 };
+        adaptedInput = new Object[] { 
CodegenTestUtil.newEmptyEvaluationContext(), new Object[] {"String", 34} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+
+        originalInput = new Object[] { new Object[] {"String", 34} };
+        adaptedInput = new Object[] { new Object[] {"String", 34} };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        originalInput = new Object[] {BigDecimal.valueOf(10), null, 
BigDecimal.valueOf(20), BigDecimal.valueOf(40),null };
+        adaptedInput = new Object[] { new Object[] { BigDecimal.valueOf(10), 
null, BigDecimal.valueOf(20), BigDecimal.valueOf(40),null } };
+        compares = new ScoreHelper.Compares(originalInput, adaptedInput, null);
+        retrieved = ScoreHelper.coercedToVarargs.applyAsInt(compares);
+        assertEquals(coercedToVarargsScore, retrieved);
+    }
+
+    @Test
+    void nullCounts() {
+        Object[] adaptedInput = new Object[] { "String" };
+        ScoreHelper.Compares compares = new ScoreHelper.Compares(null, 
adaptedInput, null);
+        int retrieved = ScoreHelper.nullCounts.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        adaptedInput = new Object[] {  };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.nullCounts.applyAsInt(compares);
+        assertEquals(0, retrieved);
+
+        adaptedInput = new Object[] { "String", null };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.nullCounts.applyAsInt(compares);
+        assertEquals(-1, retrieved);
+
+        adaptedInput = new Object[] { null };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.nullCounts.applyAsInt(compares);
+        assertEquals(-1, retrieved);
+
+        adaptedInput = new Object[] { null, new Object(), null };
+        compares = new ScoreHelper.Compares(null, adaptedInput, null);
+        retrieved = ScoreHelper.nullCounts.applyAsInt(compares);
+        assertEquals(-2, retrieved);
+    }
+
+    @Test
+    void nullCount() {
+        Random random = new Random();
+        int elements = random.nextInt(10);
+        Object[] params = new Object[elements];
+        int expectedCount = 0;
+        for (int i = 0; i < elements; i++) {
+            if (random.nextBoolean()) {
+                params[i] = null;
+                expectedCount++;
+            } else {
+                params[i] = new Object();
+            }
+        }
+        assertEquals(expectedCount, ScoreHelper.nullCount(params));
+    }
+}
\ No newline at end of file
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java
index 81127b4230..31cef2149d 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/util/CoerceUtilTest.java
@@ -24,6 +24,7 @@ import java.time.LocalDate;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -36,6 +37,46 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class CoerceUtilTest {
 
+    @Test
+    void coerceParam() {
+        // Coerce List to singleton
+        Class<?> currentIdxActualParameterType = List.class;
+        Class<?> expectedParameterType = Number.class;
+        Object valueObject = 34;
+        Object actualObject = List.of(valueObject);
+        Optional<Object> retrieved = 
CoerceUtil.coerceParam(currentIdxActualParameterType, expectedParameterType, 
actualObject);
+        assertNotNull(retrieved);
+        assertTrue(retrieved.isPresent());
+        assertEquals(valueObject, retrieved.get());
+
+        // Coerce single element to singleton list
+        currentIdxActualParameterType = Number.class;
+        expectedParameterType = List.class;
+        actualObject = 34;
+        retrieved = CoerceUtil.coerceParam(currentIdxActualParameterType, 
expectedParameterType, actualObject);
+        assertNotNull(retrieved);
+        assertTrue(retrieved.isPresent());
+        assertTrue(retrieved.get() instanceof List);
+        List lstRetrieved = (List) retrieved.get();
+        assertEquals(1, lstRetrieved.size());
+        assertEquals(actualObject, lstRetrieved.get(0));
+
+        // Coerce date to date and time
+        actualObject = LocalDate.now();
+        currentIdxActualParameterType = LocalDate.class;
+        expectedParameterType = ZonedDateTime.class;
+        retrieved = CoerceUtil.coerceParam(currentIdxActualParameterType, 
expectedParameterType, actualObject);
+        assertNotNull(retrieved);
+        assertTrue(retrieved.isPresent());
+        assertTrue(retrieved.get() instanceof ZonedDateTime);
+        ZonedDateTime zdtRetrieved = (ZonedDateTime) retrieved.get();
+        assertEquals(actualObject, zdtRetrieved.toLocalDate());
+        assertEquals(ZoneOffset.UTC, zdtRetrieved.getOffset());
+        assertEquals(0, zdtRetrieved.getHour());
+        assertEquals(0, zdtRetrieved.getMinute());
+        assertEquals(0, zdtRetrieved.getSecond());
+    }
+
     @Test
     void coerceParameterDateToDateTimeConverted() {
         Object value = LocalDate.now();
diff --git a/kie-dmn/kie-dmn-feel/src/test/resources/logback.xml 
b/kie-dmn/kie-dmn-feel/src/test/resources/logback.xml
index 3d747c98aa..752bdbd6df 100644
--- a/kie-dmn/kie-dmn-feel/src/test/resources/logback.xml
+++ b/kie-dmn/kie-dmn-feel/src/test/resources/logback.xml
@@ -29,6 +29,7 @@
   </appender>
 
   <logger name="org.kie" level="warn"/>
+  <logger name="org.kie.dmn.feel.runtime.functions" level="info"/> <!-- set to 
trace for very detailed logging <info -->
   <logger name="org.kie.dmn.feel.codegen" level="info"/> <!-- useful default 
when moving generic org.kie to <info -->
 
   <root level="warn">
diff --git 
a/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/AvgFunction.java
 
b/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/AvgFunction.java
index 3504e55339..e32471e43f 100644
--- 
a/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/AvgFunction.java
+++ 
b/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/AvgFunction.java
@@ -39,7 +39,7 @@ public class AvgFunction
     }
 
     public FEELFnResult<BigDecimal> invoke(@ParameterName("list") Number 
single) {
-        return BuiltInFunctions.getFunction(MeanFunction.class).invoke(single);
+        return 
BuiltInFunctions.getFunction(MeanFunction.class).invoke(List.of(single));
     }
 
     public FEELFnResult<BigDecimal> invoke(@ParameterName("n") Object[] list) {
diff --git 
a/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/ConcatFunction.java
 
b/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/ConcatFunction.java
index 602c2b12cc..e0bc747cbf 100644
--- 
a/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/ConcatFunction.java
+++ 
b/kie-dmn/kie-dmn-signavio/src/main/java/org/kie/dmn/signavio/feel/runtime/functions/ConcatFunction.java
@@ -20,6 +20,7 @@ package org.kie.dmn.signavio.feel.runtime.functions;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 import org.kie.dmn.api.feel.runtime.events.FEELEvent;
 import org.kie.dmn.feel.runtime.events.InvalidParametersEvent;
@@ -39,9 +40,9 @@ public class ConcatFunction
                if (list == null) {
                        return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "list", "cannot be null"));
                }
-               if (list.contains(null)) {
-                       return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "list", "cannot contain null 
values"));
-               }
+        if (list.stream().anyMatch(Objects::isNull)) {
+            return FEELFnResult.ofError(new 
InvalidParametersEvent(FEELEvent.Severity.ERROR, "list", "cannot contain null 
values"));
+        }
                
                StringBuilder sb = new StringBuilder();
                for (Object element : list) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to