This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch releases-0.12 in repository https://gitbox.apache.org/repos/asf/fory.git
commit c5fdbe6818823aa89d90a7c07e69c2ef802c5baa Author: Steven Schlansker <[email protected]> AuthorDate: Wed Sep 3 20:44:26 2025 -0700 feat(java): bean encoder implemented interfaces honor `@Ignore` (#2576) this allows you to mark methods as not properties, for example if you have a computed value with a `default` method --- .../java/org/apache/fory/annotation/Ignore.java | 4 +- .../main/java/org/apache/fory/type/Descriptor.java | 3 +- .../fory/format/encoder/RowEncoderBuilder.java | 35 ++++++++++++- .../format/encoder/ImplementInterfaceTest.java | 58 ++++++++++++++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/java/fory-core/src/main/java/org/apache/fory/annotation/Ignore.java b/java/fory-core/src/main/java/org/apache/fory/annotation/Ignore.java index 1234e2055..25e308579 100644 --- a/java/fory-core/src/main/java/org/apache/fory/annotation/Ignore.java +++ b/java/fory-core/src/main/java/org/apache/fory/annotation/Ignore.java @@ -24,7 +24,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Ignore fields just like transient. */ +/** Ignore properties just like transient. */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface Ignore {} diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java index 927ffda51..06d09a5d5 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java @@ -462,7 +462,8 @@ public class Descriptor { for (Method method : clazz.getMethods()) { if (method.getParameterCount() == 0 && method.getReturnType() != void.class - && !Modifier.isStatic(method.getModifiers())) { + && !Modifier.isStatic(method.getModifiers()) + && !method.isAnnotationPresent(Ignore.class)) { descriptorMap.put(method, new Descriptor(method)); } } diff --git a/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowEncoderBuilder.java b/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowEncoderBuilder.java index f24da862d..6a3f59922 100644 --- a/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowEncoderBuilder.java +++ b/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowEncoderBuilder.java @@ -22,10 +22,14 @@ package org.apache.fory.format.encoder; import static org.apache.fory.type.TypeUtils.CLASS_TYPE; import static org.apache.fory.type.TypeUtils.getRawType; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.SortedMap; import org.apache.arrow.vector.types.pojo.Field; @@ -335,11 +339,19 @@ public class RowEncoderBuilder extends BaseBinaryEncoderBuilder { implClass.implementsInterfaces(implClass.type(beanClass)); implClass.addField(true, implClass.type(BinaryRow.class), "row", null); + Map<String, Map<MethodType, Method>> methodsNeedingImpl = new HashMap<>(); + for (Method m : beanClass.getMethods()) { + methodsNeedingImpl + .computeIfAbsent(m.getName(), x -> new HashMap<>()) + .put(MethodType.methodType(m.getReturnType(), m.getParameterTypes()), m); + } + int numFields = schema.getFields().size(); for (int i = 0; i < numFields; i++) { Literal ordinal = Literal.ofInt(i); Descriptor d = getDescriptorByFieldName(schema.getFields().get(i).getName()); TypeRef<?> fieldType = d.getTypeRef(); + Class<?> rawFieldType = fieldType.getRawType(); Expression.Reference decodeValue = new Expression.Reference(decodeMethodName(i) + "(row)", fieldType); @@ -348,7 +360,6 @@ public class RowEncoderBuilder extends BaseBinaryEncoderBuilder { getterImpl = new Expression.Return(decodeValue); } else { String fieldName = "f" + i + "_" + d.getName(); - Class<?> rawFieldType = fieldType.getRawType(); implClass.addField(false, ctx.type(rawFieldType), fieldName, nullValue(fieldType)); Expression fieldRef = new Expression.Reference(fieldName, fieldType, true); @@ -381,12 +392,34 @@ public class RowEncoderBuilder extends BaseBinaryEncoderBuilder { } getterImpl = new Expression.ListExpression(assigner, new Expression.Return(fieldRef)); } + methodsNeedingImpl + .getOrDefault(d.getName(), new HashMap<>()) + .remove(MethodType.methodType(rawFieldType)); implClass.addMethod( d.getName(), getterImpl.genCode(implClass).code(), fieldType.getRawType()); } // Note: adding constructor captures init code, so must happen after all fields are collected implClass.addConstructor("this.row = row;", BinaryRow.class, "row"); + methodsNeedingImpl.forEach( + (methodName, signatures) -> + signatures.forEach( + (methodType, method) -> { + if (method.isDefault()) { + return; + } + Object[] params = new Object[methodType.parameterCount() * 2]; + for (int i = 0; i < methodType.parameterCount(); i++) { + params[i * 2] = methodType.parameterType(i); + params[i * 2 + 1] = "unused" + i; + } + implClass.addMethod( + methodName, + "throw new UnsupportedOperationException();", + methodType.returnType(), + params); + })); + return implClass; } diff --git a/java/fory-format/src/test/java/org/apache/fory/format/encoder/ImplementInterfaceTest.java b/java/fory-format/src/test/java/org/apache/fory/format/encoder/ImplementInterfaceTest.java index c054edeab..265a23982 100644 --- a/java/fory-format/src/test/java/org/apache/fory/format/encoder/ImplementInterfaceTest.java +++ b/java/fory-format/src/test/java/org/apache/fory/format/encoder/ImplementInterfaceTest.java @@ -29,6 +29,7 @@ import java.util.TreeSet; import lombok.Data; import org.apache.arrow.vector.types.pojo.Field; import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.Ignore; import org.apache.fory.format.row.binary.BinaryArray; import org.apache.fory.format.row.binary.BinaryRow; import org.apache.fory.format.type.DataTypes; @@ -439,4 +440,61 @@ public class ImplementInterfaceTest { Assert.assertEquals(deserializedBean.f1().get(2).f1(), 42); Assert.assertEquals(deserializedBean.f1().get(3), null); } + + public interface IgnoredMethods { + int f1(); + + @Ignore + default int doubled() { + return doubled(f1()); + } + + IgnoredMethods withF1(int f1); + + private int doubled(final int n) { + return n * 2; + } + } + + static class IgnoredMethodsImpl implements IgnoredMethods { + private final int f1; + + IgnoredMethodsImpl(final int f1) { + this.f1 = f1; + } + + @Override + public int f1() { + return f1; + } + + @Override + public int doubled() { + return f1() * 3; + } + + @Override + public IgnoredMethods withF1(final int f1) { + return new IgnoredMethodsImpl(f1); + } + } + + @Test + public void testIgnoredMethods() { + final IgnoredMethods bean1 = new IgnoredMethodsImpl(2112); + final RowEncoder<IgnoredMethods> encoder = Encoders.bean(IgnoredMethods.class); + Assert.assertEquals(encoder.schema().getFields().size(), 1); + final BinaryRow row = encoder.toRow(bean1); + final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes()); + row.pointTo(buffer, 0, buffer.size()); + final IgnoredMethods deserializedBean = encoder.fromRow(row); + Assert.assertEquals(deserializedBean.f1(), 2112); + Assert.assertEquals(deserializedBean.doubled(), 2112 * 2); + + try { + deserializedBean.withF1(100); + Assert.fail(); + } catch (final UnsupportedOperationException expected) { + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
