Search in sources :

Example 1 with GwtIncompatible

use of org.kie.dmn.model.api.GwtIncompatible in project drools by kiegroup.

the class BaseFEELFunction method invokeReflectively.

@Override
@GwtIncompatible
public Object invokeReflectively(EvaluationContext ctx, Object[] params) {
    // 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());
            }
            CandidateMethod cm = getCandidateMethod(ctx, params, isNamedParams, available);
            if (cm != null) {
                Object result = cm.apply.invoke(this, cm.actualParams);
                if (result instanceof Either) {
                    @SuppressWarnings("unchecked") Either<FEELEvent, Object> either = (Either<FEELEvent, Object>) result;
                    Object eitherResult = either.cata((left) -> {
                        ctx.notifyEvt(() -> {
                            if (left instanceof InvalidParametersEvent) {
                                InvalidParametersEvent invalidParametersEvent = (InvalidParametersEvent) left;
                                invalidParametersEvent.setNodeName(getName());
                                invalidParametersEvent.setActualParameters(Stream.of(cm.apply.getParameters()).map(p -> p.getAnnotation(ParameterName.class).value()).collect(Collectors.toList()), Arrays.asList(cm.actualParams));
                            }
                            return left;
                        });
                        return null;
                    }, Function.identity());
                    return eitherResult;
                }
                return result;
            } else {
                // 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(() -> {
                    return 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()));
            }
            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 = either.cata((left) -> {
                    ctx.notifyEvt(() -> {
                        if (left instanceof InvalidParametersEvent) {
                            InvalidParametersEvent invalidParametersEvent = (InvalidParametersEvent) left;
                            invalidParametersEvent.setNodeName(getName());
                            invalidParametersEvent.setActualParameters(IntStream.of(0, usedParams.length).mapToObj(i -> "arg" + i).collect(Collectors.toList()), Arrays.asList(usedParams));
                        }
                        return left;
                    });
                    return null;
                }, Function.identity());
                return normalizeResult(eitherResult);
            }
            return normalizeResult(result);
        }
    } catch (Exception e) {
        logger.error("Error trying to call function " + getName() + ".", e);
        ctx.notifyEvt(() -> {
            return new FEELEventBase(Severity.ERROR, "Error trying to call function " + getName() + ".", e);
        });
    }
    return null;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) InvalidParametersEvent(org.kie.dmn.feel.runtime.events.InvalidParametersEvent) FEELEventBase(org.kie.dmn.feel.runtime.events.FEELEventBase) Either(org.kie.dmn.feel.util.Either) FEELEvent(org.kie.dmn.api.feel.runtime.events.FEELEvent) GwtIncompatible(org.kie.dmn.model.api.GwtIncompatible)

Example 2 with GwtIncompatible

use of org.kie.dmn.model.api.GwtIncompatible in project drools by kiegroup.

the class BaseFEELFunction method calculateActualParams.

@GwtIncompatible
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;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) ArrayList(java.util.ArrayList) GwtIncompatible(org.kie.dmn.model.api.GwtIncompatible)

Example 3 with GwtIncompatible

use of org.kie.dmn.model.api.GwtIncompatible in project drools by kiegroup.

the class BaseFEELFunction method getCandidateMethod.

@GwtIncompatible
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 = null;
        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;
        }
        boolean found = true;
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> currentIdxActualParameterType = cm.getActualClasses()[i];
            if (currentIdxActualParameterType != null && !parameterTypes[i].isAssignableFrom(currentIdxActualParameterType)) {
                // and vice-versa
                if (Collection.class.isAssignableFrom(currentIdxActualParameterType)) {
                    Collection<?> valueCollection = (Collection<?>) actualParams[i];
                    if (valueCollection.size() == 1) {
                        Object singletonValue = valueCollection.iterator().next();
                        // re-perform the assignable-from check, this time using the element itself the singleton value from the original parameter list
                        if (singletonValue != null && parameterTypes[i].isAssignableFrom(singletonValue.getClass())) {
                            Object[] newParams = new Object[cm.getActualParams().length];
                            // can't rely on adjustForVariableParameters() have actually copied
                            System.arraycopy(cm.getActualParams(), 0, newParams, 0, cm.getActualParams().length);
                            newParams[i] = singletonValue;
                            cm.setActualParams(newParams);
                            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)) {
                        // `cm` narrower for named parameters without need of passing nulls.
                        candidate = cm;
                    } 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)) {
                        // `cm` is more narrowed, hence reflect `candidate` to be now `cm`.
                        candidate = cm;
                    }
                } else {
                // do nothing.
                }
            }
        }
    }
    return candidate;
}
Also used : NamedParameter(org.kie.dmn.feel.lang.impl.NamedParameter) Method(java.lang.reflect.Method) Collection(java.util.Collection) EvaluationContext(org.kie.dmn.feel.lang.EvaluationContext) GwtIncompatible(org.kie.dmn.model.api.GwtIncompatible)

Aggregations

NamedParameter (org.kie.dmn.feel.lang.impl.NamedParameter)3 GwtIncompatible (org.kie.dmn.model.api.GwtIncompatible)3 Method (java.lang.reflect.Method)1 ArrayList (java.util.ArrayList)1 Collection (java.util.Collection)1 FEELEvent (org.kie.dmn.api.feel.runtime.events.FEELEvent)1 EvaluationContext (org.kie.dmn.feel.lang.EvaluationContext)1 FEELEventBase (org.kie.dmn.feel.runtime.events.FEELEventBase)1 InvalidParametersEvent (org.kie.dmn.feel.runtime.events.InvalidParametersEvent)1 Either (org.kie.dmn.feel.util.Either)1