Repository: primefaces/primefaces (java)

Issue:
PropertyDescriptorResolver: add setValue, similar to getValue
to allow also subexpressions

Pull request description:
#12371 - PropertyDescriptorResolver: add setValue, similar to getValue
Fix #12371

Patch:
diff --git a/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
index d6e7f4c5c2..455ddf70f1 100644
--- a/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
+++ b/primefaces/src/main/java/org/primefaces/util/PropertyDescriptorResolver.java
@@ -23,21 +23,22 @@
  */
 package org.primefaces.util;
 
-
+import javax.faces.FacesException;
 import java.beans.IntrospectionException;
 import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
-import javax.faces.FacesException;
-
 public interface PropertyDescriptorResolver {
 
-    PropertyDescriptor get(Class<?> klazz, String name);
+    PropertyDescriptor get(Class<?> klazz, String expression);
 
-    Object getValue(Object bean, String name);
+    Object getValue(Object obj, String expression);
+
+    void setValue(Object obj, String expression, Object value);
 
     void flush();
 
@@ -64,8 +65,8 @@ public interface PropertyDescriptorResolver {
             PropertyDescriptor pd = null;
             Class<?> parent = klazz;
 
-            for (String field : NESTED_EXPRESSION_PATTERN.split(expression)) {
-                pd = getSimpleProperty(parent, field);
+            for (String property : NESTED_EXPRESSION_PATTERN.split(expression)) {
+                pd = getSimpleProperty(parent, property);
                 parent = pd.getPropertyType();
             }
 
@@ -73,16 +74,45 @@ public interface PropertyDescriptorResolver {
         }
 
         @Override
-        public Object getValue(Object bean, String expression) {
+        public Object getValue(Object obj, String expression) {
             try {
-                for (String field : NESTED_EXPRESSION_PATTERN.split(expression)) {
-                    bean = getSimpleProperty(bean.getClass(), field).getReadMethod().invoke(bean);
-                    if (bean == null) {
+                for (String property : NESTED_EXPRESSION_PATTERN.split(expression)) {
+                    obj = getSimpleProperty(obj.getClass(), property).getReadMethod().invoke(obj);
+                    if (obj == null) {
                         break;
                     }
                 }
 
-                return bean;
+                return obj;
+            }
+            catch (ReflectiveOperationException e) {
+                throw new FacesException(e);
+            }
+        }
+
+        @Override
+        public void setValue(Object obj, String expression, Object value) {
+            try {
+                String[] expressions = NESTED_EXPRESSION_PATTERN.split(expression);
+
+                for (int i = 0; i < expressions.length; i++) {
+                    String property = expressions[i];
+                    boolean lastExpression = i + 1 == expressions.length;
+
+                    if (lastExpression) {
+                        Method writeMethod = getSimpleProperty(obj.getClass(), property).getWriteMethod();
+                        if (writeMethod == null) {
+                            throw new FacesException("property '" + property + "' is readonly");
+                        }
+                        writeMethod.invoke(obj, value);
+                    }
+                    else {
+                        obj = getSimpleProperty(obj.getClass(), property).getReadMethod().invoke(obj);
+                        if (obj == null) {
+                            throw new FacesException("property '" + property + "' resolved to null");
+                        }
+                    }
+                }
             }
             catch (ReflectiveOperationException e) {
                 throw new FacesException(e);
@@ -94,9 +124,9 @@ public interface PropertyDescriptorResolver {
             pdCache.clear();
         }
 
-        private PropertyDescriptor getSimpleProperty(Class<?> klazz, String field) {
+        private PropertyDescriptor getSimpleProperty(Class<?> klazz, String property) {
             String cacheKey = klazz.getName();
-            return pdCache.computeIfAbsent(cacheKey, k -> new ConcurrentHashMap<>()).computeIfAbsent(field, k -> {
+            return pdCache.computeIfAbsent(cacheKey, k -> new ConcurrentHashMap<>()).computeIfAbsent(property, k -> {
                 try {
                     return new PropertyDescriptor(k, klazz);
                 }


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
index 8bf271fdcf..c5f1d2d3fd 100644
--- a/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/PropertyDescriptorResolverTest.java
@@ -37,7 +37,7 @@ import org.junit.jupiter.api.Test;
 class PropertyDescriptorResolverTest {
 
     @Test
-    void getPropertyDescriptor() {
+    void get() {
         PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
 
         PropertyDescriptor pd = propResolver.get(A.class, "name");
@@ -60,7 +60,7 @@ class PropertyDescriptorResolverTest {
     }
 
     @Test
-    void getValueFromPropertyDescriptor() {
+    void getValue() {
         PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
 
         B b = new B("b", true, Boolean.FALSE, DummyEnum.X);
@@ -80,7 +80,7 @@ class PropertyDescriptorResolverTest {
     }
 
     @Test
-    void getValueFromReadOnlyPropertyDescriptor() {
+    void getValueReadOnly() {
         PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
 
         F f = new F(true, false, "test");
@@ -93,7 +93,27 @@ class PropertyDescriptorResolverTest {
         assertNull(propResolver.get(f.getClass(), "readOnly3").getWriteMethod());
     }
 
-    private abstract class A {
+    @Test
+    void setValue() {
+        PropertyDescriptorResolver propResolver = new PropertyDescriptorResolver.DefaultResolver();
+
+        A a = new A(null, false, false);
+        assertNull(propResolver.getValue(a, "name"));
+        propResolver.setValue(a, "name", "Hans");
+        assertEquals("Hans", propResolver.getValue(a, "name"));
+
+        B b = new B("b", true, Boolean.FALSE, DummyEnum.X);
+        D d = new D("d", false, Boolean.FALSE, DummyEnum.Z, 123);
+        C c = new C(d);
+
+        assertEquals(123, propResolver.getValue(c, "b.foo"));
+        propResolver.setValue(c, "b.foo", 124);
+        assertEquals(124, propResolver.getValue(c, "b.foo"));
+
+        assertThrows(FacesException.class, () -> propResolver.setValue(new C(null), "b.dummyEnum", null));
+    }
+
+    private class A {
 
         private String name;
         private boolean bool1;