当参数是谓词时,EL 3.0 (JSR-341) 不工作?

EL 3.0 (JSR-341) not working when argument is a Predicate?

这个测试说明了我的问题:

public static String testMe(Predicate<String> filter) {
    return "yeah!";
}

@Test
public void testPredicateAsArgument() throws ClassNotFoundException, NoSuchMethodException {
    final ELProcessor elp = new ELProcessor();
    elp.defineFunction("", "", "test.EL3Test", "testMe");
    try {
        Object result = elp.eval("testMe(o->true)"); // IllegalArgumentException
        // ...
    } catch (javax.el.ELException ex) {
        fail(Exceptions.getCauseMessage(ex));
    }
}

它抛出:IllegalArgumentException: Cannot convert javax.el.LambdaExpression@511baa65 of type class javax.el.LambdaExpression to interface java.util.function.Predicate

这是 EL 3 的错误或限制还是我遗漏了什么?

测试版本:org.glassfish:javax.el:jar:3.0.0org.glassfish:javax.el:jar:3.0.1-b08

也发布了 as github issue

截至 2021 年 9 月 6 日的更新

通过https://github.com/eclipse-ee4j/el-ri/commit/bcd0eeb349af607eb15428c04853a0be0948f80c

实施

EL 3.0 是在 Java 1.7(Java EE 7 的目标)而非 Java 1.8 期间创建的。换句话说,它早于 Java 本机 lambda,因此不可能对它们一无所知。

最好的办法是发布下一个 EL 版本的增强问题。我们目前已经在使用 Java EE 8(目标是 Java 1.8),但还没有 EL 3.1。您必须等待 Java EE 9 才能使任何增强请求最终可能在 EL.next.

中结束

对于任何对解决方案感兴趣的人:

/**
 * {@code FunctionalInterfaceResolver} intercepts method calls with EL lambda expressions as
 * arguments which need to coerce to Java 8 functional interfaces.
 *
 * A custom `ELResolver` is always consulted before the default resolvers.
 *
 * Example usage:
 * ```` java
 * final ELProcessor elp = new ELProcessor();
 * elp.getELManager().addELResolver(new FunctionalInterfaceResolver());
 * final Object result = elp.eval("p.findResources(o->o.getContentType() eq 'image/png')");
 * ````
 *
 * @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
 */
public final class FunctionalInterfaceResolver extends ELResolver {

    @Override
    public Object invoke(final ELContext context, final Object base, final Object method,
        final Class<?>[] paramTypes, final Object[] params) {

        if (context == null || base == null || !(method instanceof String) || params == null) {
            return null;
        }
        // takes about 5ms. Try out caching if it becomes a bottleneck
        for (int i = 0; i < params.length; ++i) {
            if (params[i] instanceof javax.el.LambdaExpression) {
                for (Method m : base.getClass().getMethods()) {
                    if (m.getName().equals(method) && m.getParameterCount() == params.length) {
                        final Class[] types = m.getParameterTypes();
                        if (types[i].isAnnotationPresent(FunctionalInterface.class)) {
                            params[i] = coerceToFunctionalInterface(context,
                                (LambdaExpression)params[i], types[i]);
                        }
                    }
                }
            }
        }
        return null;
    }

    @Override
    public Class<?> getType(ELContext context, Object base, Object property) {
        return null;
    }

    @Override
    public void setValue(ELContext context, Object base, Object property, Object value) {
    }

    @Override
    public boolean isReadOnly(ELContext context, Object base, Object property) {
        return false;
    }

    @Override
    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
        return null;
    }

    @Override
    public Class<?> getCommonPropertyType(ELContext context, Object base) {
        return String.class;
    }

    @Override
    public Object convertToType(ELContext context, Object obj, Class<?> targetType) {
        if (obj instanceof LambdaExpression &&
            targetType.isAnnotationPresent(FunctionalInterface.class)) {
            context.setPropertyResolved(obj, targetType);
            return coerceToFunctionalInterface(context, (LambdaExpression)obj, targetType);
       }
       return null;
    }

    private Object coerceToFunctionalInterface(
        final ELContext context, final LambdaExpression elLambda, final Class<?> targetType) {

        assert targetType.isAnnotationPresent(FunctionalInterface.class);
        return Proxy.newProxyInstance(targetType.getClassLoader(),
            new Class[]{targetType}, (Object obj, Method method, Object[] args) -> {

            // a FunctionalInterface has exactly one abstract method
            if (Modifier.isAbstract(method.getModifiers())) {
                // the "functional method"
                return elLambda.invoke(context, args);
            } else if ("toString".equals(method.getName())) {
                return "Proxy[" + targetType.getName() + ", wrapping " +
                    elLambda.getClass().getName() + ']';
            } else {
                throw new AssertionError("Method not expected: " + method.getName());
            }
        });
    }

    @Override
    public Object getValue(ELContext context, Object base, Object property) {
        return null;
    }

}