MYFACES-4327 refactored
diff --git a/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java b/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java
index 8bcaa93..3c808d2 100644
--- a/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java
+++ b/impl/src/main/java/org/apache/myfaces/el/resolver/MethodHandleBeanELResolver.java
@@ -18,70 +18,36 @@
  */

 package org.apache.myfaces.el.resolver;

 

-import java.beans.Introspector;

-import java.beans.PropertyDescriptor;

-import java.lang.invoke.CallSite;

-import java.lang.invoke.LambdaConversionException;

-import java.lang.invoke.LambdaMetafactory;

-import java.lang.invoke.MethodHandle;

-import java.lang.invoke.MethodHandles;

-import java.lang.invoke.MethodType;

-import java.lang.reflect.Method;

 import java.util.Map;

 import java.util.Objects;

 import java.util.concurrent.ConcurrentHashMap;

-import java.util.function.BiConsumer;

-import java.util.function.Function;

-import java.util.function.ObjDoubleConsumer;

-import java.util.function.ObjIntConsumer;

-import java.util.function.ObjLongConsumer;

-import java.util.logging.Logger;

 

 import javax.el.BeanELResolver;

 import javax.el.ELContext;

 import javax.el.ELException;

-import javax.el.PropertyNotFoundException;

 import javax.el.PropertyNotWritableException;

-import org.apache.myfaces.bean.ManagedBeanExtension;

+import org.apache.myfaces.util.lang.MethodHandleUtils;

 

 public class MethodHandleBeanELResolver extends BeanELResolver

 {

-    private static final Logger LOG = Logger.getLogger(ManagedBeanExtension.class.getName());

-

-    private static Method privateLookupIn;

-

-    static

-    {

-        try

-        {

-            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class,

-                    MethodHandles.Lookup.class);

-        }

-        catch (Exception e)

-        {

-            LOG.info("MethodHandles#privateLookupIn not found or accessible. Skip using "

-                    + MethodHandleBeanELResolver.class.getName());

-        }

-    }

-

-    private final ConcurrentHashMap<String, Map<String, PropertyInfo>> cache;

+    private final ConcurrentHashMap<String, Map<String, MethodHandleUtils.LambdaPropertyDescriptor>> cache;

 

     public MethodHandleBeanELResolver()

     {

-        if (privateLookupIn == null)

+        if (MethodHandleUtils.isSupported())

         {

-            cache = null;

+            cache = new ConcurrentHashMap<>(1000);

         }

         else

         {

-            cache = new ConcurrentHashMap<>(1000);

+            cache = null;

         }

     }

 

     @Override

     public Class<?> getType(ELContext context, Object base, Object property)

     {

-        if (privateLookupIn == null)

+        if (!MethodHandleUtils.isSupported())

         {

             return super.getType(context, base, property);

         }

@@ -94,14 +60,14 @@
 

         context.setPropertyResolved(base, property);

 

-        return getPropertyInfo(base, property).type;

+        return getPropertyDescriptor(base, property).getType();

     }

 

     @SuppressWarnings("unchecked")

     @Override

     public Object getValue(ELContext context, Object base, Object property)

     {

-        if (privateLookupIn == null)

+        if (!MethodHandleUtils.isSupported())

         {

             return super.getValue(context, base, property);

         }

@@ -116,7 +82,7 @@
 

         try

         {

-            return getPropertyInfo(base, property).getter.apply(base);

+            return getPropertyDescriptor(base, property).getGetter().apply(base);

         }

         catch (Exception e)

         {

@@ -128,7 +94,7 @@
     @Override

     public void setValue(ELContext context, Object base, Object property, Object value)

     {

-        if (privateLookupIn == null)

+        if (!MethodHandleUtils.isSupported())

         {

             super.setValue(context, base, property, value);

             return;

@@ -142,8 +108,8 @@
 

         context.setPropertyResolved(base, property);

 

-        PropertyInfo propertyInfo = getPropertyInfo(base, property);

-        if (propertyInfo.setter == null)

+        MethodHandleUtils.LambdaPropertyDescriptor propertyDescriptor = getPropertyDescriptor(base, property);

+        if (propertyDescriptor.getSetter() == null)

         {

             throw new PropertyNotWritableException("Property \"" + (String) property

                     + "\" in \"" + base.getClass().getName() + "\" is not writable!");

@@ -151,7 +117,7 @@
 

         try

         {

-            propertyInfo.setter.accept(base, value);

+            propertyDescriptor.getSetter().accept(base, value);

         }

         catch (Exception e)

         {

@@ -160,17 +126,9 @@
     }

 

     @Override

-    public Object invoke(ELContext context, Object base, Object method, Class<?>[] paramTypes, Object[] params)

-    {

-        // just use the original BeanELResolver method

-        // it's higher effort to implement this

-        return super.invoke(context, base, method, paramTypes, params);

-    }

-

-    @Override

     public boolean isReadOnly(ELContext context, Object base, Object property)

     {

-        if (privateLookupIn == null)

+        if (!MethodHandleUtils.isSupported())

         {

             return super.isReadOnly(context, base, property);

         }

@@ -183,13 +141,13 @@
 

         context.setPropertyResolved(base, property);

 

-        return getPropertyInfo(base, property).setter == null;

+        return getPropertyDescriptor(base, property).getSetter() == null;

     }

 

     @Override

     public Class<?> getCommonPropertyType(ELContext context, Object base)

     {

-        if (privateLookupIn == null)

+        if (!MethodHandleUtils.isSupported())

         {

             return super.getCommonPropertyType(context, base);

         }

@@ -202,182 +160,11 @@
         return null;

     }

 

-    protected class PropertyInfo

+    protected MethodHandleUtils.LambdaPropertyDescriptor getPropertyDescriptor(Object base, Object property)

     {

-        Class<?> type;

-        Function getter;

-        BiConsumer setter;

+        Map<String, MethodHandleUtils.LambdaPropertyDescriptor> beanCache = cache.computeIfAbsent(

+                base.getClass().getName(), k -> MethodHandleUtils.getLambdaPropertyDescriptors(base.getClass()));

+        return beanCache.get((String) property);

     }

 

-    protected PropertyInfo getPropertyInfo(Object base, Object property)

-    {

-        Map<String, PropertyInfo> beanCache = cache.computeIfAbsent(base.getClass().getName(),

-                k -> new ConcurrentHashMap<>());

-        return beanCache.computeIfAbsent((String) property, k -> initPropertyInfo(base.getClass(), k));

-    }

-

-    protected PropertyInfo initPropertyInfo(Class<?> target, String fieldName)

-    {

-

-        PropertyInfo info = new PropertyInfo();

-

-        try

-        {

-            PropertyDescriptor pd = null;

-            for (PropertyDescriptor cpd : Introspector.getBeanInfo(target, Object.class).getPropertyDescriptors())

-            {

-                if (fieldName.equals(cpd.getName()))

-                {

-                    pd = cpd;

-                }

-            }

-

-            if (pd == null)

-            {

-                throw new PropertyNotFoundException("Property \"" + fieldName + "\" not found on \""

-                        + target.getName() + "\"");

-            }

-

-            info.type = pd.getPropertyType();

-

-            MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupIn.invoke(null, target,

-                    MethodHandles.lookup());

-

-            Method getter = pd.getReadMethod();

-            if (getter != null)

-            {

-                MethodHandle getterHandle = lookup.unreflect(getter);

-                CallSite getterCallSite = LambdaMetafactory.metafactory(lookup,

-                        "apply",

-                        MethodType.methodType(Function.class),

-                        MethodType.methodType(Object.class, Object.class),

-                        getterHandle,

-                        getterHandle.type());

-                info.getter = (Function) getterCallSite.getTarget().invokeExact();

-            }

-

-            Method setter = pd.getWriteMethod();

-            if (setter != null)

-            {

-                MethodHandle setterHandle = lookup.unreflect(setter);

-                info.setter = createSetter(lookup, info, setterHandle);

-            }

-        }

-        catch (Throwable e)

-        {

-            throw new ELException(e);

-        }

-

-        return info;

-    }

-

-    @SuppressWarnings("unchecked")

-    protected BiConsumer createSetter(MethodHandles.Lookup lookup, PropertyInfo propertyInfo,

-            MethodHandle setterHandle)

-            throws LambdaConversionException, Throwable

-    {

-        // special handling for primitives required, see https://dzone.com/articles/setters-method-handles-and-java-11

-        if (propertyInfo.type.isPrimitive())

-        {

-            if (propertyInfo.type == double.class)

-            {

-                ObjDoubleConsumer consumer = (ObjDoubleConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjDoubleConsumer.class, double.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (double) b);

-            }

-            else if (propertyInfo.type == int.class)

-            {

-                ObjIntConsumer consumer = (ObjIntConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjIntConsumer.class, int.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (int) b);

-            }

-            else if (propertyInfo.type == long.class)

-            {

-                ObjLongConsumer consumer = (ObjLongConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjLongConsumer.class, long.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (long) b);

-            }

-            else if (propertyInfo.type == float.class)

-            {

-                ObjFloatConsumer consumer = (ObjFloatConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjFloatConsumer.class, float.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (float) b);

-            }

-            else if (propertyInfo.type == byte.class)

-            {

-                ObjByteConsumer consumer = (ObjByteConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjByteConsumer.class, byte.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (byte) b);

-            }

-            else if (propertyInfo.type == char.class)

-            {

-                ObjCharConsumer consumer = (ObjCharConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjCharConsumer.class, char.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (char) b);

-            }

-            else if (propertyInfo.type == short.class)

-            {

-                ObjShortConsumer consumer = (ObjShortConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjShortConsumer.class, short.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (short) b);

-            }

-            else if (propertyInfo.type == boolean.class)

-            {

-                ObjBooleanConsumer consumer = (ObjBooleanConsumer) createSetterCallSite(

-                        lookup, setterHandle, ObjBooleanConsumer.class, boolean.class).getTarget().invokeExact();

-                return (a, b) -> consumer.accept(a, (boolean) b);

-            }

-            else

-            {

-                throw new RuntimeException("Type is not supported yet: " + propertyInfo.type.getName());

-            }

-        }

-        else

-        {

-            return (BiConsumer) createSetterCallSite(lookup, setterHandle, BiConsumer.class, Object.class).getTarget()

-                    .invokeExact();

-        }

-    }

-

-    protected CallSite createSetterCallSite(MethodHandles.Lookup lookup, MethodHandle setter, Class<?> interfaceType,

-            Class<?> valueType)

-            throws LambdaConversionException

-    {

-        return LambdaMetafactory.metafactory(lookup,

-                "accept",

-                MethodType.methodType(interfaceType),

-                MethodType.methodType(void.class, Object.class, valueType),

-                setter,

-                setter.type());

-    }

-

-    @FunctionalInterface

-    public interface ObjFloatConsumer<T extends Object>

-    {

-        public void accept(T t, float i);

-    }

-

-    @FunctionalInterface

-    public interface ObjByteConsumer<T extends Object>

-    {

-        public void accept(T t, byte i);

-    }

-

-    @FunctionalInterface

-    public interface ObjCharConsumer<T extends Object>

-    {

-        public void accept(T t, char i);

-    }

-

-    @FunctionalInterface

-    public interface ObjShortConsumer<T extends Object>

-    {

-        public void accept(T t, short i);

-    }

-

-    @FunctionalInterface

-    public interface ObjBooleanConsumer<T extends Object>

-    {

-        public void accept(T t, boolean i);

-    }

 }

diff --git a/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java b/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java
new file mode 100644
index 0000000..df62baa
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/util/lang/MethodHandleUtils.java
@@ -0,0 +1,236 @@
+/*

+ * 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.apache.myfaces.util.lang;

+

+import java.beans.Introspector;

+import java.beans.PropertyDescriptor;

+import java.lang.invoke.CallSite;

+import java.lang.invoke.LambdaConversionException;

+import java.lang.invoke.LambdaMetafactory;

+import java.lang.invoke.MethodHandle;

+import java.lang.invoke.MethodHandles;

+import java.lang.invoke.MethodType;

+import java.lang.reflect.Method;

+import java.util.HashMap;

+import java.util.function.BiConsumer;

+import java.util.function.Function;

+import java.util.function.ObjDoubleConsumer;

+import java.util.function.ObjIntConsumer;

+import java.util.function.ObjLongConsumer;

+import javax.el.ELException;

+

+public class MethodHandleUtils

+{

+    private static Method privateLookupIn;

+

+    static

+    {

+        try

+        {

+            privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class,

+                    MethodHandles.Lookup.class);

+        }

+        catch (Exception e)

+        {

+        }

+    }

+

+    public static boolean isSupported()

+    {

+        return privateLookupIn != null;

+    }

+    

+    public static class LambdaPropertyDescriptor

+    {

+        private Class<?> type;

+        private Function getter;

+        private BiConsumer setter;

+

+        public Class<?> getType()

+        {

+            return type;

+        }

+

+        public Function getGetter()

+        {

+            return getter;

+        }

+

+        public BiConsumer getSetter()

+        {

+            return setter;

+        }

+    }

+

+    public static HashMap<String, LambdaPropertyDescriptor> getLambdaPropertyDescriptors(Class<?> target)

+    {

+        try

+        {            

+            PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(target).getPropertyDescriptors();

+            HashMap<String, LambdaPropertyDescriptor> properties = new HashMap<>(propertyDescriptors.length);

+

+            for (PropertyDescriptor pd : Introspector.getBeanInfo(target).getPropertyDescriptors())

+            {

+                LambdaPropertyDescriptor lpd = new LambdaPropertyDescriptor();

+                lpd.type = pd.getPropertyType();

+                

+                MethodHandles.Lookup lookup = (MethodHandles.Lookup) privateLookupIn.invoke(null, target,

+                        MethodHandles.lookup());

+

+                Method getter = pd.getReadMethod();

+                if (getter != null)

+                {

+                    MethodHandle getterHandle = lookup.unreflect(getter);

+                    CallSite getterCallSite = LambdaMetafactory.metafactory(lookup,

+                            "apply",

+                            MethodType.methodType(Function.class),

+                            MethodType.methodType(Object.class, Object.class),

+                            getterHandle,

+                            getterHandle.type());

+                    lpd.getter = (Function) getterCallSite.getTarget().invokeExact();

+                }

+

+                Method setter = pd.getWriteMethod();

+                if (setter != null)

+                {

+                    MethodHandle setterHandle = lookup.unreflect(setter);

+                    lpd.setter = createSetter(lookup, lpd, setterHandle);

+                }

+                

+                properties.put(pd.getName(), lpd);

+            }

+            

+            return properties;

+        }

+        catch (Throwable e)

+        {

+            throw new ELException(e);

+        }

+    }

+

+    @SuppressWarnings("unchecked")

+    protected static BiConsumer createSetter(MethodHandles.Lookup lookup, LambdaPropertyDescriptor propertyInfo,

+            MethodHandle setterHandle)

+            throws LambdaConversionException, Throwable

+    {

+        // special handling for primitives required, see https://dzone.com/articles/setters-method-handles-and-java-11

+        if (propertyInfo.type.isPrimitive())

+        {

+            if (propertyInfo.type == double.class)

+            {

+                ObjDoubleConsumer consumer = (ObjDoubleConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjDoubleConsumer.class, double.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (double) b);

+            }

+            else if (propertyInfo.type == int.class)

+            {

+                ObjIntConsumer consumer = (ObjIntConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjIntConsumer.class, int.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (int) b);

+            }

+            else if (propertyInfo.type == long.class)

+            {

+                ObjLongConsumer consumer = (ObjLongConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjLongConsumer.class, long.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (long) b);

+            }

+            else if (propertyInfo.type == float.class)

+            {

+                ObjFloatConsumer consumer = (ObjFloatConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjFloatConsumer.class, float.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (float) b);

+            }

+            else if (propertyInfo.type == byte.class)

+            {

+                ObjByteConsumer consumer = (ObjByteConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjByteConsumer.class, byte.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (byte) b);

+            }

+            else if (propertyInfo.type == char.class)

+            {

+                ObjCharConsumer consumer = (ObjCharConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjCharConsumer.class, char.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (char) b);

+            }

+            else if (propertyInfo.type == short.class)

+            {

+                ObjShortConsumer consumer = (ObjShortConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjShortConsumer.class, short.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (short) b);

+            }

+            else if (propertyInfo.type == boolean.class)

+            {

+                ObjBooleanConsumer consumer = (ObjBooleanConsumer) createSetterCallSite(

+                        lookup, setterHandle, ObjBooleanConsumer.class, boolean.class).getTarget().invokeExact();

+                return (a, b) -> consumer.accept(a, (boolean) b);

+            }

+            else

+            {

+                throw new RuntimeException("Type is not supported yet: " + propertyInfo.type.getName());

+            }

+        }

+        else

+        {

+            return (BiConsumer) createSetterCallSite(lookup, setterHandle, BiConsumer.class, Object.class).getTarget()

+                    .invokeExact();

+        }

+    }

+

+    protected static CallSite createSetterCallSite(MethodHandles.Lookup lookup, MethodHandle setter,

+            Class<?> interfaceType, Class<?> valueType)

+            throws LambdaConversionException

+    {

+        return LambdaMetafactory.metafactory(lookup,

+                "accept",

+                MethodType.methodType(interfaceType),

+                MethodType.methodType(void.class, Object.class, valueType),

+                setter,

+                setter.type());

+    }

+

+    @FunctionalInterface

+    public interface ObjFloatConsumer<T extends Object>

+    {

+        public void accept(T t, float i);

+    }

+

+    @FunctionalInterface

+    public interface ObjByteConsumer<T extends Object>

+    {

+        public void accept(T t, byte i);

+    }

+

+    @FunctionalInterface

+    public interface ObjCharConsumer<T extends Object>

+    {

+        public void accept(T t, char i);

+    }

+

+    @FunctionalInterface

+    public interface ObjShortConsumer<T extends Object>

+    {

+        public void accept(T t, short i);

+    }

+

+    @FunctionalInterface

+    public interface ObjBooleanConsumer<T extends Object>

+    {

+        public void accept(T t, boolean i);

+    }

+}