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

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new cc6e60b47d Marshall module improvements
cc6e60b47d is described below

commit cc6e60b47db330c1d7e5b378ae2d878d276d6495
Author: James Bognar <[email protected]>
AuthorDate: Sat Dec 6 10:25:27 2025 -0500

    Marshall module improvements
---
 .../apache/juneau/commons/reflect/Property.java    |  12 +-
 .../src/main/java/org/apache/juneau/ClassMeta.java | 152 +++++++++++++++++----
 .../org/apache/juneau/annotation/NameProperty.java |   4 +-
 .../org/apache/juneau/parser/ParserSession.java    |   2 +-
 .../docs/topics/02.04.07.NamePropertyAnnotation.md |   4 +-
 ...ipTest.java => NameProperty_RoundTripTest.java} |  43 +-----
 ...Test.java => ParentProperty_RoundTripTest.java} |  53 +------
 .../annotation/NamePropertyAnnotation_Test.java    | 103 ++++++++++++++
 .../annotation/ParentPropertyAnnotation_Test.java  |  63 +++++++++
 .../juneau/commons/reflect/Property_Test.java      |  12 +-
 10 files changed, 317 insertions(+), 131 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/Property.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/Property.java
index 864a4a40ce..fc8785327c 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/Property.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/Property.java
@@ -146,20 +146,20 @@ public class Property<T, V> {
        }
 
        /**
-        * Returns <jk>true</jk> if this property has a getter (producer).
+        * Returns <jk>true</jk> if this property can be read.
         *
-        * @return <jk>true</jk> if a producer is defined.
+        * @return <jk>true</jk> if this property can be read.
         */
-       public boolean hasGetter() {
+       public boolean canRead() {
                return producer != null;
        }
 
        /**
-        * Returns <jk>true</jk> if this property has a setter (consumer).
+        * Returns <jk>true</jk> if this property can be written.
         *
-        * @return <jk>true</jk> if a consumer is defined.
+        * @return <jk>true</jk> if this property can be written.
         */
-       public boolean hasSetter() {
+       public boolean canWrite() {
                return consumer != null;
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index b3053fc349..d180f07f53 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -162,10 +162,10 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
        private final ClassMeta<?> keyType;                                     
                                        // If MAP, the key class type.
        private final SimpleReadWriteLock lock = new SimpleReadWriteLock(false);
        private final Supplier<MarshalledFilter> marshalledFilter;
-       private final Supplier<Property<T,String>> namePropertySetter;          
                                  // The method to set the name on an object 
(if it has one).
+       private final Supplier<Property<T,Object>> nameProperty;                
                            // The method to set the name on an object (if it 
has one).
        private final Supplier<ConstructorInfo> noArgConstructor;               
                                        // The no-arg constructor for this 
class (if it has one).
        private final String notABeanReason;                                    
                                        // If this isn't a bean, the reason why.
-       private final Supplier<Property<T,?>> parentPropertySetter;             
                             // The method to set the parent on an object (if 
it has one).
+       private final Supplier<Property<T,Object>> parentProperty;              
                            // The method to set the parent on an object (if it 
has one).
        private final Map<String,Optional<?>> properties = new 
ConcurrentHashMap<>();
        private final Mutater<String,T> stringMutater;
        private final Supplier<ConstructorInfo> stringConstructor;              
                                       // The X(String) constructor (if it has 
one).
@@ -254,8 +254,8 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
 
                        fromStringMethod = memoize(()->findFromStringMethod());
                        exampleMethod = memoize(()->findExampleMethod());
-                       parentPropertySetter = 
memoize(()->findParentPropertySetter());
-                       namePropertySetter = 
memoize(()->findNamePropertySetter());
+                       parentProperty = memoize(()->findParentProperty());
+                       nameProperty = memoize(()->findNameProperty());
                        exampleField = memoize(()->findExampleField());
                        noArgConstructor = memoize(()->findNoArgConstructor());
                        stringConstructor = 
memoize(()->findStringConstructor());
@@ -376,8 +376,8 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
                this.stringMutater = null;
                this.fromStringMethod = memoize(()->findFromStringMethod());
                this.exampleMethod = memoize(()->findExampleMethod());
-               this.parentPropertySetter = 
memoize(()->findParentPropertySetter());
-               this.namePropertySetter = memoize(()->findNamePropertySetter());
+               this.parentProperty = memoize(()->findParentProperty());
+               this.nameProperty = memoize(()->findNameProperty());
                this.exampleField = memoize(()->findExampleField());
                this.noArgConstructor = memoize(()->findNoArgConstructor());
                this.stringConstructor = memoize(()->findStringConstructor());
@@ -416,8 +416,8 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
                this.exampleMethod = mainType.exampleMethod;
                this.args = null;
                this.stringMutater = mainType.stringMutater;
-               this.parentPropertySetter = mainType.parentPropertySetter;
-               this.namePropertySetter = mainType.namePropertySetter;
+               this.parentProperty = mainType.parentProperty;
+               this.nameProperty = mainType.nameProperty;
                this.exampleField = mainType.exampleField;
                this.noArgConstructor = mainType.noArgConstructor;
                this.stringConstructor = mainType.stringConstructor;
@@ -742,7 +742,7 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
         *      The method or field  annotated with {@link NameProperty 
@NameProperty} or <jk>null</jk> if method does not
         *      exist.
         */
-       public Property<T,String> getNameProperty() { return 
namePropertySetter.get(); }
+       public Property<T,Object> getNameProperty() { return 
nameProperty.get(); }
 
        /**
         * Returns the reason why this class is not a bean, or <jk>null</jk> if 
it is a bean.
@@ -772,7 +772,7 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
         *      The method or field annotated with {@link ParentProperty 
@ParentProperty} or <jk>null</jk> if method does not
         *      exist.
         */
-       public Property<T,?> getParentProperty() { return 
parentPropertySetter.get(); }
+       public Property<T,Object> getParentProperty() { return 
parentProperty.get(); }
 
        /**
         * Returns a calculated property on this context.
@@ -1568,25 +1568,77 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
                return 
MarshalledFilter.create(inner()).applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList())).build();
        }
 
-       private Property<T,String> findNamePropertySetter() {
+       private Property<T,Object> findNameProperty() {
                var ap = beanContext.getAnnotationProvider();
 
                var s = getAllFields()
                        .stream()
-                       .filter(x -> x.getFieldType().is(String.class) && 
ap.has(NameProperty.class, x))
+                       .filter(x -> ap.has(NameProperty.class, x))
                        .map(x -> x.accessible())
-                       .map(x -> Property.<T,String>create().field(x).build())
+                       .map(x -> Property.<T,Object>create().field(x).build())
                        .findFirst();
 
                if (s.isPresent()) return s.get();
 
-               return getAllMethods()
+               var builder = Property.<T,Object>create();
+
+               // Look for setter method (1 parameter) with @NameProperty
+               var setterMethod = getAllMethods()
                        .stream()
                        .filter(x -> ap.has(NameProperty.class, x) && 
x.hasNumParameters(1))
-                       .map(x -> x.accessible())
-                       .map(x -> Property.<T,String>create().setter(x).build())
-                       .findFirst()
-                       .orElse(null);
+                       .findFirst();
+
+               if (setterMethod.isPresent()) {
+                       builder.setter(setterMethod.get().accessible());
+                       
+                       // Try to find a corresponding getter method (even if 
not annotated)
+                       // If setter is "setName", look for "getName" or 
"isName"
+                       var setterName = setterMethod.get().getSimpleName();
+                       if (setterName.startsWith("set") && setterName.length() 
> 3) {
+                               var propertyName = setterName.substring(3);
+                               var getterName1 = "get" + propertyName;
+                               var getterName2 = "is" + propertyName;
+
+                               var getter = getAllMethods()
+                                       .stream()
+                                       .filter(x -> !x.isStatic() && 
x.hasNumParameters(0) && 
+                                               (x.hasName(getterName1) || 
x.hasName(getterName2)) &&
+                                               
!x.getReturnType().is(Void.TYPE))
+                                       .findFirst();
+
+                               if (getter.isPresent()) {
+                                       
builder.getter(getter.get().accessible());
+                               } else {
+                                       // Try to find a field with the 
property name (lowercase first letter)
+                                       var fieldName = 
Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+                                       var field = getAllFields()
+                                               .stream()
+                                               .filter(x -> !x.isStatic() && 
x.hasName(fieldName))
+                                               .findFirst();
+
+                                       if (field.isPresent()) {
+                                               var f = 
field.get().accessible();
+                                               builder.getter(obj -> 
(Object)f.get(obj));
+                                       }
+                               }
+                       }
+               }
+
+               // Look for getter method (0 parameters, non-void return) with 
@NameProperty
+               var getterMethod = getAllMethods()
+                       .stream()
+                       .filter(x -> ap.has(NameProperty.class, x) && 
x.hasNumParameters(0) && !x.getReturnType().is(Void.TYPE))
+                       .findFirst();
+
+               if (getterMethod.isPresent()) {
+                       builder.getter(getterMethod.get().accessible());
+               }
+
+               // Return null if neither setter nor getter was found
+               if (setterMethod.isEmpty() && getterMethod.isEmpty())
+                       return null;
+
+               return builder.build();
        }
 
        private ConstructorInfo findNoArgConstructor() {
@@ -1608,7 +1660,7 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
                        .orElse(null);
        }
 
-       private Property<T,?> findParentPropertySetter() {
+       private Property<T,Object> findParentProperty() {
                var ap = beanContext.getAnnotationProvider();
 
                var s = getAllFields()
@@ -1620,13 +1672,65 @@ public class ClassMeta<T> extends ClassInfoTyped<T> {
 
                if (s.isPresent()) return s.get();
 
-               return getAllMethods()
+               var builder = Property.<T,Object>create();
+
+               // Look for setter method (1 parameter) with @ParentProperty
+               var setterMethod = getAllMethods()
                        .stream()
                        .filter(x -> ap.has(ParentProperty.class, x) && 
x.hasNumParameters(1))
-                       .map(x -> x.accessible())
-                       .map(x -> Property.<T,Object>create().setter(x).build())
-                       .findFirst()
-                       .orElse(null);
+                       .findFirst();
+
+               if (setterMethod.isPresent()) {
+                       builder.setter(setterMethod.get().accessible());
+                       
+                       // Try to find a corresponding getter method (even if 
not annotated)
+                       // If setter is "setParent", look for "getParent" or 
"isParent"
+                       var setterName = setterMethod.get().getSimpleName();
+                       if (setterName.startsWith("set") && setterName.length() 
> 3) {
+                               var propertyName = setterName.substring(3);
+                               var getterName1 = "get" + propertyName;
+                               var getterName2 = "is" + propertyName;
+
+                               var getter = getAllMethods()
+                                       .stream()
+                                       .filter(x -> !x.isStatic() && 
x.hasNumParameters(0) && 
+                                               (x.hasName(getterName1) || 
x.hasName(getterName2)) &&
+                                               
!x.getReturnType().is(Void.TYPE))
+                                       .findFirst();
+
+                               if (getter.isPresent()) {
+                                       
builder.getter(getter.get().accessible());
+                               } else {
+                                       // Try to find a field with the 
property name (lowercase first letter)
+                                       var fieldName = 
Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
+                                       var field = getAllFields()
+                                               .stream()
+                                               .filter(x -> !x.isStatic() && 
x.hasName(fieldName))
+                                               .findFirst();
+
+                                       if (field.isPresent()) {
+                                               var f = 
field.get().accessible();
+                                               builder.getter(obj -> 
(Object)f.get(obj));
+                                       }
+                               }
+                       }
+               }
+
+               // Look for getter method (0 parameters, non-void return) with 
@ParentProperty
+               var getterMethod = getAllMethods()
+                       .stream()
+                       .filter(x -> ap.has(ParentProperty.class, x) && 
x.hasNumParameters(0) && !x.getReturnType().is(Void.TYPE))
+                       .findFirst();
+
+               if (getterMethod.isPresent()) {
+                       builder.getter(getterMethod.get().accessible());
+               }
+
+               // Return null if neither setter nor getter was found
+               if (setterMethod.isEmpty() && getterMethod.isEmpty())
+                       return null;
+
+               return builder.build();
        }
 
        private ConstructorInfo findStringConstructor() {
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/NameProperty.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/NameProperty.java
index 1120ac7bcd..5637c1e09a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/NameProperty.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/NameProperty.java
@@ -32,8 +32,8 @@ import java.lang.annotation.*;
  * <h5 class='section'>Requirements:</h5>
  * <ul class='spaced-list'>
  *     <li>Must be an <strong>instance</strong> method or field (not static)
- *     <li>For methods: Must accept exactly one parameter of type <c>String</c>
- *     <li>For fields: Must be of type <c>String</c>
+ *     <li>For methods: Must accept exactly one parameter (any type)
+ *     <li>For fields: Can be any type
  *     <li>The method or field does not need to be public (will be made 
accessible automatically)
  * </ul>
  *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
index 4f22375eb8..3a32cf5f9a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
@@ -217,7 +217,7 @@ public class ParserSession extends BeanSession {
         * @param name The name to set.
         * @throws ExecutableException Exception occurred on invoked 
constructor/method/field.
         */
-       protected static final void setName(ClassMeta<?> cm, Object o, Object 
name) throws ExecutableException {
+       protected static final <T> void setName(ClassMeta<?> cm, Object o, 
Object name) throws ExecutableException {
                if (nn(cm)) {
                        Property m = cm.getNameProperty();
                        if (nn(m))
diff --git a/juneau-docs/docs/topics/02.04.07.NamePropertyAnnotation.md 
b/juneau-docs/docs/topics/02.04.07.NamePropertyAnnotation.md
index 5e4f429acd..ecaf584f03 100644
--- a/juneau-docs/docs/topics/02.04.07.NamePropertyAnnotation.md
+++ b/juneau-docs/docs/topics/02.04.07.NamePropertyAnnotation.md
@@ -10,8 +10,8 @@ This annotation is used by parsers to automatically set the 
name/key of an objec
 ## Requirements
 
 - Must be an **instance** method or field (not static)
-- For methods: Must accept exactly one parameter of type `String`
-- For fields: Must be of type `String`
+- For methods: Must accept exactly one parameter (any type)
+- For fields: Can be any type
 - The method or field does not need to be public (will be made accessible 
automatically)
 
 ## Usage
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/NameProperty_RoundTripTest.java
old mode 100755
new mode 100644
similarity index 71%
copy from 
juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
copy to 
juneau-utest/src/test/java/org/apache/juneau/a/rttests/NameProperty_RoundTripTest.java
index ff686269fa..e73c2c1608
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/NameProperty_RoundTripTest.java
@@ -29,7 +29,7 @@ import org.junit.jupiter.params.provider.*;
  * Tests designed to serialize and parse objects to make sure we end up
  * with the same objects for all serializers and parsers.
  */
-class ObjectsWithSpecialMethods_RoundTripTest extends RoundTripTest_Base {
+class NameProperty_RoundTripTest extends RoundTripTest_Base {
 
        
//====================================================================================================
        // @NameProperty method.
@@ -73,44 +73,5 @@ class ObjectsWithSpecialMethods_RoundTripTest extends 
RoundTripTest_Base {
                        return this;
                }
        }
+}
 
-       
//====================================================================================================
-       // @ParentProperty method.
-       
//====================================================================================================
-
-       @ParameterizedTest
-       @MethodSource("testers")
-       void a02_parentProperty(RoundTrip_Tester t) throws Exception {
-               var x = new B().init();
-               x = t.roundTrip(x);
-               if (t.isValidationOnly())
-                       return;
-               assertEquals(x.f1, x.b2.parent.f1);
-       }
-
-       public static class B {
-               public int f1;
-               public B2 b2;
-
-               B init() {
-                       f1 = 1;
-                       b2 = new B2().init();
-                       return this;
-               }
-
-       }
-       public static class B2 {
-               B parent;
-               public int f2;
-
-               @ParentProperty
-               protected void setParent(B v) {
-                       parent = v;
-               }
-
-               B2 init() {
-                       f2 = 2;
-                       return this;
-               }
-       }
-}
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
old mode 100755
new mode 100644
similarity index 61%
rename from 
juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
index ff686269fa..c460a87f03
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ObjectsWithSpecialMethods_RoundTripTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
@@ -16,11 +16,8 @@
  */
 package org.apache.juneau.a.rttests;
 
-import static org.apache.juneau.junit.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
-import java.util.*;
-
 import org.apache.juneau.annotation.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
@@ -29,50 +26,7 @@ import org.junit.jupiter.params.provider.*;
  * Tests designed to serialize and parse objects to make sure we end up
  * with the same objects for all serializers and parsers.
  */
-class ObjectsWithSpecialMethods_RoundTripTest extends RoundTripTest_Base {
-
-       
//====================================================================================================
-       // @NameProperty method.
-       
//====================================================================================================
-
-       @ParameterizedTest
-       @MethodSource("testers")
-       void a01_nameProperty(RoundTrip_Tester t) throws Exception {
-               var x = new A().init();
-               x = t.roundTrip(x);
-               assertBean(x, "a2{f2},m{k1{f2}}", "{2},{{2}}");
-               if (t.isValidationOnly())
-                       return;
-               assertBean(x, "a2{name}", "{a2}");
-               assertBean(x, "m{k1{name}}", "{{k1}}");
-       }
-
-       public static class A {
-               public A2 a2;
-               public Map<String,A2> m;
-
-               A init() {
-                       a2 = new A2().init();
-                       m = new LinkedHashMap<>();
-                       m.put("k1", new A2().init());
-                       return this;
-               }
-
-       }
-       public static class A2 {
-               String name;
-               public int f2;
-
-               @NameProperty
-               protected void setName(String name) {
-                       this.name = name;
-               }
-
-               A2 init() {
-                       f2 = 2;
-                       return this;
-               }
-       }
+class ParentProperty_RoundTripTest extends RoundTripTest_Base {
 
        
//====================================================================================================
        // @ParentProperty method.
@@ -80,7 +34,7 @@ class ObjectsWithSpecialMethods_RoundTripTest extends 
RoundTripTest_Base {
 
        @ParameterizedTest
        @MethodSource("testers")
-       void a02_parentProperty(RoundTrip_Tester t) throws Exception {
+       void a01_parentProperty(RoundTrip_Tester t) throws Exception {
                var x = new B().init();
                x = t.roundTrip(x);
                if (t.isValidationOnly())
@@ -113,4 +67,5 @@ class ObjectsWithSpecialMethods_RoundTripTest extends 
RoundTripTest_Base {
                        return this;
                }
        }
-}
\ No newline at end of file
+}
+
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/NamePropertyAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/NamePropertyAnnotation_Test.java
index be6be6407b..f8455e4cd0 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/NamePropertyAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/NamePropertyAnnotation_Test.java
@@ -107,4 +107,107 @@ class NamePropertyAnnotation_Test extends TestBase {
                assertNotEqualsAny(a1.hashCode(), 0, -1);
                assertEqualsAll(a1.hashCode(), d1.hashCode(), d2.hashCode());
        }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Property functionality tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       public static class TestBeanWithNamePropertyField {
+               @NameProperty
+               public String name;
+       }
+
+       public static class TestBeanWithNamePropertyMethod {
+               private String name;
+
+               @NameProperty
+               protected void setName(String name) {
+                       this.name = name;
+               }
+
+               public String getName() {
+                       return name;
+               }
+       }
+
+       @Test void e01_namePropertyField() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = bc.getClassMeta(TestBeanWithNamePropertyField.class);
+               var prop = cm.getNameProperty();
+               assertNotNull(prop, "NameProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithNamePropertyField();
+               prop.set(bean, "testName");
+               assertEquals("testName", prop.get(bean));
+               assertEquals("testName", bean.name);
+       }
+
+       @Test void e02_namePropertyMethod() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = bc.getClassMeta(TestBeanWithNamePropertyMethod.class);
+               var prop = cm.getNameProperty();
+               assertNotNull(prop, "NameProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithNamePropertyMethod();
+               prop.set(bean, "testName");
+               assertEquals("testName", prop.get(bean));
+               assertEquals("testName", bean.getName());
+       }
+
+       @Test void e03_namePropertyNotFound() {
+               var bc = BeanContext.DEFAULT;
+               var cm = bc.getClassMeta(String.class);
+               var prop = cm.getNameProperty();
+               assertNull(prop, "NameProperty should not be found on String 
class");
+       }
+
+       public static class TestBeanWithNamePropertyIntegerField {
+               @NameProperty
+               public Integer id;
+       }
+
+       public static class TestBeanWithNamePropertyIntegerMethod {
+               private Integer id;
+
+               @NameProperty
+               protected void setId(Integer id) {
+                       this.id = id;
+               }
+
+               public Integer getId() {
+                       return id;
+               }
+       }
+
+       @Test void e04_namePropertyWithIntegerField() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = 
bc.getClassMeta(TestBeanWithNamePropertyIntegerField.class);
+               var prop = cm.getNameProperty();
+               assertNotNull(prop, "NameProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithNamePropertyIntegerField();
+               prop.set(bean, 42);
+               assertEquals(42, prop.get(bean));
+               assertEquals(Integer.valueOf(42), bean.id);
+       }
+
+       @Test void e05_namePropertyWithIntegerMethod() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = 
bc.getClassMeta(TestBeanWithNamePropertyIntegerMethod.class);
+               var prop = cm.getNameProperty();
+               assertNotNull(prop, "NameProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithNamePropertyIntegerMethod();
+               prop.set(bean, 42);
+               assertEquals(42, prop.get(bean));
+               assertEquals(Integer.valueOf(42), bean.getId());
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/ParentPropertyAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/ParentPropertyAnnotation_Test.java
index e8dab64323..51fd3565a2 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/annotation/ParentPropertyAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/annotation/ParentPropertyAnnotation_Test.java
@@ -107,4 +107,67 @@ class ParentPropertyAnnotation_Test extends TestBase {
                assertNotEqualsAny(a1.hashCode(), 0, -1);
                assertEqualsAll(a1.hashCode(), d1.hashCode(), d2.hashCode());
        }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Property functionality tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       public static class ParentBean {
+               public int value = 42;
+       }
+
+       public static class TestBeanWithParentPropertyField {
+               @ParentProperty
+               public ParentBean parent;
+       }
+
+       public static class TestBeanWithParentPropertyMethod {
+               private ParentBean parent;
+
+               @ParentProperty
+               protected void setParent(ParentBean parent) {
+                       this.parent = parent;
+               }
+
+               public ParentBean getParent() {
+                       return parent;
+               }
+       }
+
+       @Test void e01_parentPropertyField() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = bc.getClassMeta(TestBeanWithParentPropertyField.class);
+               var prop = cm.getParentProperty();
+               assertNotNull(prop, "ParentProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithParentPropertyField();
+               var parent = new ParentBean();
+               prop.set(bean, parent);
+               assertSame(parent, prop.get(bean));
+               assertSame(parent, bean.parent);
+       }
+
+       @Test void e02_parentPropertyMethod() throws Exception {
+               var bc = BeanContext.DEFAULT;
+               var cm = 
bc.getClassMeta(TestBeanWithParentPropertyMethod.class);
+               var prop = cm.getParentProperty();
+               assertNotNull(prop, "ParentProperty should be found");
+               assertTrue(prop.canWrite(), "Should have setter");
+               assertTrue(prop.canRead(), "Should have getter");
+
+               var bean = new TestBeanWithParentPropertyMethod();
+               var parent = new ParentBean();
+               prop.set(bean, parent);
+               assertSame(parent, prop.get(bean));
+               assertSame(parent, bean.getParent());
+       }
+
+       @Test void e03_parentPropertyNotFound() {
+               var bc = BeanContext.DEFAULT;
+               var cm = bc.getClassMeta(String.class);
+               var prop = cm.getParentProperty();
+               assertNull(prop, "ParentProperty should not be found on String 
class");
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/Property_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/Property_Test.java
index 90835bc664..8249d78fc3 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/Property_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/Property_Test.java
@@ -90,8 +90,8 @@ class Property_Test extends TestBase {
                var obj = new TestClass();
                obj.setPublicField("testValue");
                assertEquals("testValue", prop.get(obj));
-               assertTrue(prop.hasGetter());
-               assertFalse(prop.hasSetter());
+               assertTrue(prop.canRead());
+               assertFalse(prop.canWrite());
        }
 
        
//====================================================================================================
@@ -106,8 +106,8 @@ class Property_Test extends TestBase {
                var obj = new TestClass();
                prop.set(obj, "testValue");
                assertEquals("testValue", obj.getPublicField());
-               assertFalse(prop.hasGetter());
-               assertTrue(prop.hasSetter());
+               assertFalse(prop.canRead());
+               assertTrue(prop.canWrite());
        }
 
        
//====================================================================================================
@@ -423,8 +423,8 @@ class Property_Test extends TestBase {
                        .build();
 
                assertNotNull(prop);
-               assertTrue(prop.hasGetter());
-               assertTrue(prop.hasSetter());
+               assertTrue(prop.canRead());
+               assertTrue(prop.canWrite());
        }
 
        
//====================================================================================================

Reply via email to