From f74da7743fe85a36367ad1e5f002a1c2318fdf97 Mon Sep 17 00:00:00 2001 From: tangcent Date: Sat, 29 Jul 2023 10:48:51 +0800 Subject: [PATCH] refactor: Refactor ReflectivePropertyOperator to support unencapsulated field --- .../AsmReflectivePropertyOperator.java | 88 +++++++++++++ .../ReflectiveFieldAdapterMethodInvoker.java | 111 ++++++++++++++++ .../reflect/ReflectivePropertyOperator.java | 121 +++++++++++++++--- .../cn/crane4j/core/util/ReflectUtils.java | 36 +++++- ...ectiveDisassembleOperationHandlerTest.java | 9 +- .../AsmReflectivePropertyOperatorTest.java | 66 ++++++++-- ...flectiveFieldAdapterMethodInvokerTest.java | 68 ++++++++++ .../ReflectivePropertyOperatorTest.java | 106 +++++++++++++-- 8 files changed, 562 insertions(+), 43 deletions(-) create mode 100644 crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvoker.java create mode 100644 crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvokerTest.java diff --git a/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperator.java b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperator.java index d6f8b0c4..b4d54346 100644 --- a/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperator.java +++ b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperator.java @@ -3,11 +3,14 @@ import cn.crane4j.core.support.MethodInvoker; import cn.crane4j.core.support.converter.ConverterManager; import cn.crane4j.core.util.CollectionUtils; +import com.esotericsoftware.reflectasm.FieldAccess; import com.esotericsoftware.reflectasm.MethodAccess; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -23,6 +26,11 @@ public class AsmReflectivePropertyOperator extends ReflectivePropertyOperator { */ private final Map, MethodAccess> methodAccessCaches = new ConcurrentHashMap<>(); + /** + * field access caches. + */ + private final Map, FieldAccess> fieldAccessCaches = new ConcurrentHashMap<>(); + /** * Create an {@link AsmReflectivePropertyOperator} comparator * @@ -70,4 +78,84 @@ public Object invoke(@Nullable Object target, @Nullable Object... args) { return methodAccess.invoke(target, methodIndex, args); } } + + @Override + protected MethodInvoker createInvokerForSetter(Class targetType, String propertyName, Field field) { + if (Modifier.isPrivate(field.getModifiers())) { + return super.createInvokerForSetter(targetType, propertyName, field); + } + FieldAccess access = CollectionUtils.computeIfAbsent(fieldAccessCaches, targetType, FieldAccess::get); + try { + int fieldIndex = access.getIndex(field.getName()); + return new ReflectAsmFieldAdapterSetterInvoker(access, fieldIndex); + } catch (IllegalArgumentException e) { + return super.createInvokerForSetter(targetType, propertyName, field); + } + } + + @Override + protected MethodInvoker createInvokerForGetter(Class targetType, String propertyName, Field field) { + if (Modifier.isPrivate(field.getModifiers())) { + return super.createInvokerForGetter(targetType, propertyName, field); + } + FieldAccess access = CollectionUtils.computeIfAbsent(fieldAccessCaches, targetType, FieldAccess::get); + try { + int fieldIndex = access.getIndex(field.getName()); + return new ReflectAsmFieldAdapterGetterInvoker(access, fieldIndex); + } catch (IllegalArgumentException e) { + return super.createInvokerForGetter(targetType, propertyName, field); + } + } + + /** + * {@link MethodInvoker} implementation based on {@link FieldAccess} + */ + public static abstract class ReflectAsmFieldInvoker implements MethodInvoker { + + private final FieldAccess fieldAccess; + private final int fieldIndex; + + public ReflectAsmFieldInvoker(FieldAccess fieldAccess, int fieldIndex) { + this.fieldAccess = fieldAccess; + this.fieldIndex = fieldIndex; + } + + @Override + public Object invoke(@Nullable Object target, @Nullable Object... args) { + return invoke(fieldAccess, fieldIndex, target, args); + } + + protected abstract Object invoke(FieldAccess fieldAccess, int fieldIndex, @Nullable Object target, @Nullable Object... args); + } + + /** + * An implementation of the {@link ReflectAsmFieldInvoker} for getter. + */ + public static class ReflectAsmFieldAdapterGetterInvoker extends ReflectAsmFieldInvoker { + + public ReflectAsmFieldAdapterGetterInvoker(FieldAccess fieldAccess, int fieldIndex) { + super(fieldAccess, fieldIndex); + } + + @Override + protected Object invoke(FieldAccess fieldAccess, int fieldIndex, @Nullable Object target, @Nullable Object... args) { + return fieldAccess.get(target, fieldIndex); + } + } + + /** + * An implementation of the {@link ReflectAsmFieldInvoker} for setter. + */ + public static class ReflectAsmFieldAdapterSetterInvoker extends ReflectAsmFieldInvoker { + + public ReflectAsmFieldAdapterSetterInvoker(FieldAccess fieldAccess, int fieldIndex) { + super(fieldAccess, fieldIndex); + } + + @Override + protected Object invoke(FieldAccess fieldAccess, int fieldIndex, @Nullable Object target, @Nullable Object... args) { + fieldAccess.set(target, fieldIndex, args[0]); + return null; + } + } } diff --git a/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvoker.java b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvoker.java new file mode 100644 index 00000000..fa1b6f9c --- /dev/null +++ b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvoker.java @@ -0,0 +1,111 @@ +package cn.crane4j.core.support.reflect; + +import cn.crane4j.core.support.MethodInvoker; +import cn.crane4j.core.util.ReflectUtils; +import lombok.RequiredArgsConstructor; + +import java.lang.reflect.Field; + +/** + * Adapter class that implements the {@link MethodInvoker} interface with fields + * without explicit getter and setter methods. + * + * @author tangcent + */ +@RequiredArgsConstructor +public abstract class ReflectiveFieldAdapterMethodInvoker implements MethodInvoker { + + private final Field field; + + /** + * Create a getter {@link ReflectiveFieldAdapterMethodInvoker} from the given field. + * + * @param field the field to access + * @return the FieldAdapterMethodInvoker for getting the field value + */ + public static ReflectiveFieldAdapterMethodInvoker createGetter(Field field) { + ReflectUtils.setAccessible(field); + return new ReflectiveFieldGetterInvoker(field); + } + + /** + * Create a setter {@link ReflectiveFieldAdapterMethodInvoker} from the given field. + * + * @param field the field to access + * @return the FieldAdapterMethodInvoker for setting the field value + */ + public static ReflectiveFieldAdapterMethodInvoker createSetter(Field field) { + ReflectUtils.setAccessible(field); + return new ReflectiveFieldSetterInvoker(field); + } + + @Override + public Object invoke(Object target, Object... args) { + return accessField(target, field, args); + } + + /** + * Get or set the field value using reflection. + * + * @param target the object on which to invoke the method + * @param field the field to access + * @param args the arguments to pass to the method + * @return the result of the method invocation + */ + protected abstract Object accessField(Object target, Field field, Object... args); + + /** + * An implementation of the {@link ReflectiveFieldAdapterMethodInvoker} for getter. + */ + private static class ReflectiveFieldGetterInvoker extends ReflectiveFieldAdapterMethodInvoker { + + public ReflectiveFieldGetterInvoker(Field field) { + super(field); + } + + /** + * Gets the value of the field using reflection. + * + * @param target the object on which to invoke the method + * @param field the field to access + * @param args the arguments to pass to the method (ignored) + * @return the value of the field + */ + @Override + protected Object accessField(Object target, Field field, Object... args) { + try { + return field.get(target); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Failed to access field: " + field.getName(), e); + } + } + } + + /** + * An implementation of the {@link ReflectiveFieldAdapterMethodInvoker} for setter. + */ + private static class ReflectiveFieldSetterInvoker extends ReflectiveFieldAdapterMethodInvoker { + + public ReflectiveFieldSetterInvoker(Field field) { + super(field); + } + + /** + * Sets the value of the field using reflection. + * + * @param target the object on which to invoke the method + * @param field the field to access + * @param args the arguments to pass to the method (one argument, the value to set the field to) + * @return null (the result of the method invocation) + */ + @Override + protected Object accessField(Object target, Field field, Object... args) { + try { + field.set(target, args[0]); + return null; + } catch (IllegalAccessException e) { + throw new IllegalStateException("Failed to access field: " + field.getName(), e); + } + } + } +} diff --git a/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperator.java b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperator.java index 033f22a1..034899b7 100644 --- a/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperator.java +++ b/crane4j-core/src/main/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperator.java @@ -8,10 +8,9 @@ import lombok.Setter; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; /** * A {@link PropertyOperator} abstract implementation class. @@ -53,11 +52,19 @@ public ReflectivePropertyOperator(@Nullable ConverterManager converterManager) { @Override public MethodInvoker findGetter(Class targetType, String propertyName) { Method method = findGetterMethod(targetType, propertyName); - Asserts.isFalse( - Objects.isNull(method) && throwIfNoMatchedMethod, - "No getter method found for property [{}] in [{}] ", propertyName, targetType.getName() - ); - return resolveInvoker(targetType, propertyName, method); + MethodInvoker methodInvoker = resolveInvoker(targetType, propertyName, method); + if (methodInvoker != null) { + return methodInvoker; + } + + Field field = ReflectUtils.getField(targetType, propertyName); + MethodInvoker methodInvokerForGetter = resolveInvokerForGetter(targetType, propertyName, field); + if (methodInvokerForGetter != null) { + return methodInvokerForGetter; + } + + Asserts.isFalse(throwIfNoMatchedMethod, "No getter method found for property [{}] in [{}] ", propertyName, targetType.getName()); + return null; } /** @@ -71,11 +78,19 @@ public MethodInvoker findGetter(Class targetType, String propertyName) { @Override public MethodInvoker findSetter(Class targetType, String propertyName) { Method method = findSetterMethod(targetType, propertyName); - Asserts.isFalse( - Objects.isNull(method) && throwIfNoMatchedMethod, - "No setter method found for property [{}] in [{}] ", propertyName, targetType.getName() - ); - return resolveInvoker(targetType, propertyName, method); + MethodInvoker methodInvoker = resolveInvoker(targetType, propertyName, method); + if (methodInvoker != null) { + return methodInvoker; + } + + Field field = ReflectUtils.getField(targetType, propertyName); + MethodInvoker methodInvokerForSetter = resolveInvokerForSetter(targetType, propertyName, field); + if (methodInvokerForSetter != null) { + return methodInvokerForSetter; + } + + Asserts.isFalse(throwIfNoMatchedMethod, "No setter method found for property [{}] in [{}] ", propertyName, targetType.getName()); + return null; } /** @@ -87,10 +102,14 @@ public MethodInvoker findSetter(Class targetType, String propertyName) { */ @Nullable protected Method findSetterMethod(Class targetType, String propertyName) { - return Optional.ofNullable(ReflectUtils.getField(targetType, propertyName)) - .map(field -> ReflectUtils.findSetterMethod(targetType, field)) - .flatMap(Function.identity()) + Field field = ReflectUtils.getField(targetType, propertyName); + if (Objects.isNull(field)) { + return ReflectUtils.findSetterMethod(targetType, propertyName) + .orElse(null); + } else { + return ReflectUtils.findSetterMethod(targetType, field) .orElse(null); + } } /** @@ -102,10 +121,14 @@ protected Method findSetterMethod(Class targetType, String propertyName) { */ @Nullable protected Method findGetterMethod(Class targetType, String propertyName) { - return Optional.ofNullable(ReflectUtils.getField(targetType, propertyName)) - .map(field -> ReflectUtils.findGetterMethod(targetType, field)) - .flatMap(Function.identity()) + Field field = ReflectUtils.getField(targetType, propertyName); + if (Objects.isNull(field)) { + return ReflectUtils.findGetterMethod(targetType, propertyName) .orElse(null); + } else { + return ReflectUtils.findGetterMethod(targetType, field) + .orElse(null); + } } /** @@ -121,6 +144,30 @@ protected MethodInvoker createInvoker(Class targetType, String propertyName, return ReflectiveMethodInvoker.create(null, method, false); } + /** + * Creates a {@link MethodInvoker} for setting the value of the specified field. + * + * @param targetType target type + * @param propertyName property name + * @param field field to be set. + * @return The {@link MethodInvoker} instance for setting the value of the specified field. + */ + protected MethodInvoker createInvokerForSetter(Class targetType, String propertyName, Field field) { + return ReflectiveFieldAdapterMethodInvoker.createSetter(field); + } + + /** + * Creates a {@link MethodInvoker} for getting the value of the specified field. + * + * @param targetType target type + * @param propertyName property name + * @param field field to be got. + * @return The {@link MethodInvoker} instance for getting the value of the specified field. + */ + protected MethodInvoker createInvokerForGetter(Class targetType, String propertyName, Field field) { + return ReflectiveFieldAdapterMethodInvoker.createGetter(field); + } + /** * Resolve the invoker which finally to used. * @@ -145,4 +192,42 @@ protected final MethodInvoker resolveInvoker(Class targetType, String propert } return ParameterConvertibleMethodInvoker.create(invoker, converterManager, method.getParameterTypes()); } + + /** + * Resolve invoker which finally be used to set the value of the specified field. + * + * @param targetType target type + * @param propertyName property name + * @param field field to be set. + * @return the {@link MethodInvoker} instance for setting the value of the specified field. + */ + @Nullable + protected final MethodInvoker resolveInvokerForSetter(Class targetType, String propertyName, @Nullable Field field) { + if (Objects.isNull(field)) { + return null; + } + MethodInvoker invoker = createInvokerForSetter(targetType, propertyName, field); + if (Objects.isNull(invoker)) { + return null; + } + if (Objects.isNull(converterManager)) { + return invoker; + } + return ParameterConvertibleMethodInvoker.create(invoker, converterManager, new Class[] {field.getType()}); + } + + /** + * Resolve invoker which finally be used to get the value of the specified field. + * + * @param targetType target type + * @param propertyName property name + * @param field field to be got. + * @return the {@link MethodInvoker} instance for getting the value of the specified field. + */ + protected final MethodInvoker resolveInvokerForGetter(Class targetType, String propertyName, @Nullable Field field) { + if (Objects.isNull(field)) { + return null; + } + return createInvokerForGetter(targetType, propertyName, field); + } } diff --git a/crane4j-core/src/main/java/cn/crane4j/core/util/ReflectUtils.java b/crane4j-core/src/main/java/cn/crane4j/core/util/ReflectUtils.java index ae981e48..8cb1ca24 100644 --- a/crane4j-core/src/main/java/cn/crane4j/core/util/ReflectUtils.java +++ b/crane4j-core/src/main/java/cn/crane4j/core/util/ReflectUtils.java @@ -318,6 +318,40 @@ public static Optional findSetterMethod(Class beanType, Field field) return Optional.ofNullable(getMethod(beanType, booleanSetterName, fieldType)); } + /** + * find setter method + * + * @param beanType bean's type + * @param fieldName field's name + * @return java.util.Optional + */ + public static Optional findSetterMethod(Class beanType, String fieldName) { + // find setXXX method + String setterName = StringUtils.upperFirstAndAddPrefix(fieldName, SET_PREFIX); + Optional method = findMethod(beanType, setterName, 1); + if (method.isPresent()) { + return method; + } + + // find fluent method + Optional fluentSetter = findMethod(beanType, fieldName, 1); + if (fluentSetter.isPresent()) { + return fluentSetter; + } + + // find isXXX method + String booleanSetterName = StringUtils.upperFirstAndAddPrefix(fieldName, IS_PREFIX); + return findMethod(beanType, booleanSetterName, 1); + } + + public static Optional findMethod(Class beanType, String methodName, int parameterCount) { + Method[] methods = getMethods(beanType); + return Stream.of(methods) + .filter(m -> m.getName().equals(methodName)) + .filter(m -> m.getParameterCount() == parameterCount) + .findFirst(); + } + // ====================== annotation ====================== /** @@ -498,7 +532,7 @@ public static T getFieldValue(Object target, Field field) { } } - private static void setAccessible(T accessibleObject) { + public static void setAccessible(T accessibleObject) { if (!accessibleObject.isAccessible()) { accessibleObject.setAccessible(true); } diff --git a/crane4j-core/src/test/java/cn/crane4j/core/executor/handler/ReflectiveDisassembleOperationHandlerTest.java b/crane4j-core/src/test/java/cn/crane4j/core/executor/handler/ReflectiveDisassembleOperationHandlerTest.java index 8352fa6d..674e4c31 100644 --- a/crane4j-core/src/test/java/cn/crane4j/core/executor/handler/ReflectiveDisassembleOperationHandlerTest.java +++ b/crane4j-core/src/test/java/cn/crane4j/core/executor/handler/ReflectiveDisassembleOperationHandlerTest.java @@ -1,7 +1,6 @@ package cn.crane4j.core.executor.handler; import cn.crane4j.annotation.Disassemble; -import cn.crane4j.core.exception.Crane4jException; import cn.crane4j.core.executor.BaseExecutorTest; import cn.crane4j.core.parser.BeanOperations; import cn.crane4j.core.parser.operation.DisassembleOperation; @@ -78,11 +77,13 @@ public void processWhenExceptionalCase() { Assert.assertEquals("noneGetter", operation.getKey()); // 输入null - Assert.assertTrue(handler.process(operation, null).isEmpty()); + Assert.assertTrue(handler.process(operation, null) + .isEmpty()); // 没有getter方法 - Runnable runnable = () -> handler.process(operation, Collections.singleton(new Bean())); - Assert.assertThrows(Crane4jException.class, runnable::run); + Bean bean = new Bean(); + bean.noneGetter = new Bean(); + Assert.assertEquals(1,handler.process(operation, Collections.singleton(bean)).size()); } @Accessors(chain = true) diff --git a/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperatorTest.java b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperatorTest.java index ff705fca..80026cb7 100644 --- a/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperatorTest.java +++ b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/AsmReflectivePropertyOperatorTest.java @@ -1,6 +1,6 @@ package cn.crane4j.core.support.reflect; -import cn.crane4j.core.support.MethodInvoker; +import cn.crane4j.core.exception.Crane4jException; import cn.crane4j.core.support.converter.SimpleConverterManager; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,40 +19,86 @@ public class AsmReflectivePropertyOperatorTest { @Test public void readProperty() { - Foo foo = new Foo(12, true); + Foo foo = new Foo(12, true, "intact", "open", "shade"); + Foo.shared = "shared"; + Assert.assertNull(operator.readProperty(Foo.class, foo, "noneField")); Assert.assertEquals(12, operator.readProperty(Foo.class, foo, "id")); Assert.assertEquals(true, operator.readProperty(Foo.class, foo, "flag")); + Assert.assertEquals("intact", operator.readProperty(Foo.class, foo, "intact")); + Assert.assertEquals("open", operator.readProperty(Foo.class, foo, "open")); + Assert.assertEquals("shade", operator.readProperty(Foo.class, foo, "shade")); + Assert.assertEquals("shared", operator.readProperty(Foo.class, foo, "shared")); } @Test public void findGetter() { - MethodInvoker getter = operator.findGetter(Foo.class, "id"); - Assert.assertNotNull(getter); + Assert.assertNotNull(operator.findGetter(Foo.class, "id")); + Assert.assertNotNull(operator.findGetter(Foo.class, "open")); + Assert.assertNotNull(operator.findGetter(Foo.class, "shared")); Assert.assertNull(operator.findGetter(Foo.class, "none")); + + operator.setThrowIfNoMatchedMethod(true); + Assert.assertThrows(Crane4jException.class, () -> operator.findGetter(Foo.class, "none")); + operator.setThrowIfNoMatchedMethod(false); } @Test public void writeProperty() { - Foo foo = new Foo(1, true); + Foo foo = new Foo(1, true, "intact", "open", "shade"); + operator.writeProperty(Foo.class, foo, "noneField", null); operator.writeProperty(Foo.class, foo, "id", 2); Assert.assertEquals((Integer)2, foo.getId()); operator.writeProperty(Foo.class, foo, "flag", false); - Assert.assertFalse(foo.isFlag()); + Assert.assertFalse(foo.flag); + operator.writeProperty(Foo.class, foo, "intact", "other"); + Assert.assertEquals("other", foo.getIntact()); + operator.writeProperty(Foo.class, foo, "shade", "another"); + Assert.assertEquals("another", foo.getShade()); + operator.writeProperty(Foo.class, foo, "open", "closed"); + Assert.assertEquals("closed", foo.open); + operator.writeProperty(Foo.class, foo, "shared", "changed"); + Assert.assertEquals("changed", Foo.shared); + } @Test public void findSetter() { - MethodInvoker setter = operator.findSetter(Foo.class, "id"); - Assert.assertNotNull(setter); + Assert.assertNotNull(operator.findSetter(Foo.class, "id")); + Assert.assertNotNull(operator.findSetter(Foo.class, "open")); + Assert.assertNotNull(operator.findSetter(Foo.class, "shared")); Assert.assertNull(operator.findSetter(Foo.class, "none")); + + operator.setThrowIfNoMatchedMethod(true); + Assert.assertThrows(Crane4jException.class, () -> operator.findSetter(Foo.class, "none")); + operator.setThrowIfNoMatchedMethod(false); } - @Getter - @Setter @AllArgsConstructor private static class Foo { + + @Getter private Integer id; + + @Setter private boolean flag; + + @Setter + @Getter + private String intact; + + public String open; + + private String _shade; + + public static String shared; + + public String getShade() { + return _shade; + } + + public void setShade(String shade) { + _shade = shade; + } } } diff --git a/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvokerTest.java b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvokerTest.java new file mode 100644 index 00000000..4e5e680c --- /dev/null +++ b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectiveFieldAdapterMethodInvokerTest.java @@ -0,0 +1,68 @@ +package cn.crane4j.core.support.reflect; + +import cn.crane4j.core.support.MethodInvoker; +import org.junit.Test; + +import java.lang.reflect.Field; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +/** + * test for {@link ReflectiveFieldAdapterMethodInvoker} + */ +public class ReflectiveFieldAdapterMethodInvokerTest { + + private static class MyClass { + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } + + @Test + public void testCreateGetter() throws NoSuchFieldException { + MyClass obj = new MyClass(); + obj.setValue(42); + + Field field = MyClass.class.getDeclaredField("value"); + ReflectiveFieldAdapterMethodInvoker invoker = ReflectiveFieldAdapterMethodInvoker.createGetter(field); + + assertEquals(42, invoker.invoke(obj)); + } + + @Test + public void testCreateSetter() throws NoSuchFieldException { + MyClass obj = new MyClass(); + + Field field = MyClass.class.getDeclaredField("value"); + ReflectiveFieldAdapterMethodInvoker invoker = ReflectiveFieldAdapterMethodInvoker.createSetter(field); + + invoker.invoke(obj, 42); + + assertEquals(42, obj.getValue()); + } + + @Test + public void testCreateGetterWithInaccessibleField() throws NoSuchFieldException { + MyClass obj = new MyClass(); + Field field = MyClass.class.getDeclaredField("value"); + MethodInvoker getter = ReflectiveFieldAdapterMethodInvoker.createGetter(field); + field.setAccessible(false); + assertThrows(IllegalStateException.class, () -> getter.invoke(obj)); + } + + @Test + public void testCreateSetterWithInaccessibleField() throws NoSuchFieldException { + MyClass obj = new MyClass(); + Field field = MyClass.class.getDeclaredField("value"); + MethodInvoker setter = ReflectiveFieldAdapterMethodInvoker.createSetter(field); + field.setAccessible(false); + assertThrows(IllegalStateException.class, () -> setter.invoke(obj, 42)); + } +} \ No newline at end of file diff --git a/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperatorTest.java b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperatorTest.java index 7a11fe70..adb2f7d1 100644 --- a/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperatorTest.java +++ b/crane4j-core/src/test/java/cn/crane4j/core/support/reflect/ReflectivePropertyOperatorTest.java @@ -6,9 +6,16 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.val; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Assert; import org.junit.Test; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + /** * test for {@link ReflectivePropertyOperator} * @@ -20,17 +27,21 @@ public class ReflectivePropertyOperatorTest { @Test public void readProperty() { - Foo foo = new Foo(12, true); - operator.readProperty(Foo.class, foo, "noneField"); + Foo foo = new Foo(12, true, "intact", "shade"); + Foo.shared = "shared"; + Assert.assertNull(operator.readProperty(Foo.class, foo, "noneField")); Assert.assertEquals(12, operator.readProperty(Foo.class, foo, "id")); Assert.assertEquals(true, operator.readProperty(Foo.class, foo, "flag")); + Assert.assertEquals("intact", operator.readProperty(Foo.class, foo, "intact")); + Assert.assertEquals("shade", operator.readProperty(Foo.class, foo, "shade")); + Assert.assertEquals("shared", operator.readProperty(Foo.class, foo, "shared")); } @Test public void findGetter() { - MethodInvoker getter = operator.findGetter(Foo.class, "id"); - Assert.assertNotNull(getter); + Assert.assertNotNull(operator.findGetter(Foo.class, "id")); Assert.assertNull(operator.findGetter(Foo.class, "none")); + Assert.assertNotNull(operator.findGetter(Foo.class, "shared")); operator.setThrowIfNoMatchedMethod(true); Assert.assertThrows(Crane4jException.class, () -> operator.findGetter(Foo.class, "none")); @@ -39,20 +50,29 @@ public void findGetter() { @Test public void writeProperty() { - Foo foo = new Foo(1, true); + Foo foo = new Foo(1, true, "intact", "shade"); operator.writeProperty(Foo.class, foo, "noneField", null); operator.writeProperty(Foo.class, foo, "id", 2); Assert.assertEquals((Integer)2, foo.getId()); operator.writeProperty(Foo.class, foo, "flag", false); - Assert.assertFalse(foo.isFlag()); + Assert.assertFalse(foo.flag); + operator.writeProperty(Foo.class, foo, "intact", "other"); + Assert.assertEquals("other", foo.getIntact()); + operator.writeProperty(Foo.class, foo, "shade", "another"); + Assert.assertEquals("another", foo.getShade()); + operator.writeProperty(Foo.class, foo, "shared", "changed"); + Assert.assertEquals("changed", Foo.shared); } @Test public void findSetter() { operator.setConverterManager(null); - MethodInvoker setter = operator.findSetter(Foo.class, "id"); - Assert.assertNotNull(setter); + Assert.assertNotNull(operator.findSetter(Foo.class, "id")); + Assert.assertNotNull(operator.findSetter(Foo.class, "flag")); + Assert.assertNotNull(operator.findSetter(Foo.class, "intact")); + Assert.assertNotNull(operator.findSetter(Foo.class, "shade")); + Assert.assertNotNull(operator.findSetter(Foo.class, "shared")); Assert.assertNull(operator.findSetter(Foo.class, "none")); operator.setThrowIfNoMatchedMethod(true); @@ -60,12 +80,78 @@ public void findSetter() { operator.setThrowIfNoMatchedMethod(false); } - @Getter - @Setter + @Test + public void testCustomReflectivePropertyOperator() { + CustomReflectivePropertyOperator operator = new CustomReflectivePropertyOperator(); + Assert.assertNotNull(operator.findSetter(Foo.class, "id")); + Assert.assertNull(operator.findSetter(Foo.class, "flag")); + Assert.assertNull(operator.findSetter(Foo.class, "shade")); + Assert.assertNull(operator.findSetter(Foo.class, "none")); + + Foo foo = new Foo(1, true, "intact", "shade"); + operator.writeProperty(Foo.class, foo, "id", 2); + Assert.assertEquals((Integer)2, foo.getId()); + operator.writeProperty(Foo.class, foo, "flag", false); + Assert.assertTrue(foo.flag); + operator.writeProperty(Foo.class, foo, "intact", "other"); + Assert.assertEquals("other", foo.getIntact()); + operator.writeProperty(Foo.class, foo, "shade", "another"); + Assert.assertEquals("shade", foo.getShade()); + + operator.setThrowIfNoMatchedMethod(true); + Assert.assertThrows(Crane4jException.class, () -> operator.findSetter(Foo.class, "none")); + operator.setThrowIfNoMatchedMethod(false); + } + @AllArgsConstructor private static class Foo { + + @Getter private Integer id; + + @Setter private boolean flag; + + @Setter + @Getter + private String intact; + + private String _shade; + + public static String shared; + + public String getShade() { + return _shade; + } + + public void setShade(String shade) { + _shade = shade; + } + } + + private static class CustomReflectivePropertyOperator extends ReflectivePropertyOperator { + + private static final List immutableFields = Arrays.asList("flag", "shade"); + + public CustomReflectivePropertyOperator() { + super(new HutoolConverterManager()); + } + + @Override + protected @Nullable MethodInvoker createInvoker(Class targetType, String propertyName, Method method) { + if (immutableFields.contains(propertyName)) { + return null; + } + return super.createInvoker(targetType, propertyName, method); + } + + @Override + protected MethodInvoker createInvokerForSetter(Class targetType, String propertyName, Field field) { + if (immutableFields.contains(propertyName)) { + return null; + } + return super.createInvokerForSetter(targetType, propertyName, field); + } } }