diff --git a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/CompatibleExample.java b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/CompatibleExample.java index 58cd833fb6..798a90d8d8 100644 --- a/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/CompatibleExample.java +++ b/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/CompatibleExample.java @@ -19,7 +19,9 @@ package org.apache.fory.graalvm; +import java.io.Serializable; import org.apache.fory.Fory; +import org.apache.fory.util.Preconditions; public class CompatibleExample { static Fory fory; @@ -38,6 +40,8 @@ private static Fory createFory() { .build(); // register and generate serializer code. fory.register(Foo.class); + fory.register(Outer.class); + fory.register(Outer.Inner.class); fory.ensureSerializersCompiled(); return fory; } @@ -45,10 +49,35 @@ private static Fory createFory() { public static void main(String[] args) { System.out.println("CompatibleExample started"); Example.test(fory); + testInnerClass(fory); System.out.println("CompatibleExample succeed 1/2"); // Test new created Fory at runtime fory = createFory(); Example.test(fory); + testInnerClass(fory); System.out.println("CompatibleExample succeed"); } + + private static void testInnerClass(Fory fory) { + Outer outer = new Outer(); + outer.value = 7; + Outer.Inner inner = outer.new Inner(); + inner.value = 5; + Outer.Inner copy = (Outer.Inner) fory.deserialize(fory.serialize(inner)); + Preconditions.checkArgument(copy.value == 5); + Preconditions.checkArgument(copy.outer().value == 7); + Preconditions.checkArgument(copy.outer() != outer); + } + + public static class Outer { + public int value; + + public class Inner implements Serializable { + public int value; + + Outer outer() { + return Outer.this; + } + } + } } diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java index 7544fc2353..ae466b6aab 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java @@ -666,7 +666,10 @@ private static Class loadClass(String className) { /** Returns an Expression that create a new java object of type {@link CodecBuilder#beanClass}. */ protected Expression newBean() { // TODO allow default access-level class. + // A non-static inner class has no no-arg constructor and `NewInstance` would need an outer + // instance, so allocate it without a constructor like JDK25+ does. if (sourcePublicAccessible(beanClass) + && (Modifier.isStatic(beanClass.getModifiers()) || !beanClass.isMemberClass()) && (JdkVersion.MAJOR_VERSION < 25 || ReflectionUtils.hasPublicNoArgConstructor(beanClass))) { return new Expression.NewInstance(beanType); diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/CodegenCompatibleSerializerTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/CodegenCompatibleSerializerTest.java index 9396127f71..c73aace422 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/CodegenCompatibleSerializerTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/CodegenCompatibleSerializerTest.java @@ -24,8 +24,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.concurrent.PriorityBlockingQueue; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.fory.Fory; @@ -369,4 +371,54 @@ public void testWriteCompatibleMap(boolean referenceTracking, boolean enableCode ((MapFields) (fory.deserialize(fory2.serialize(mapFields)))).toCanEqual(), mapFields.toCanEqual()); } + + public static class InnerClassHolder { + public int value; + public Inner inner; + + public class Inner { + public int value; + + InnerClassHolder outer() { + return InnerClassHolder.this; + } + } + } + + @Test + public void testNonStaticInnerClass() { + Fory fory = foryBuilder().withRefTracking(true).withCodegen(true).build(); + InnerClassHolder holder = new InnerClassHolder(); + holder.value = 1; + holder.inner = holder.new Inner(); + holder.inner.value = 2; + InnerClassHolder copy = serDe(fory, holder); + Assert.assertEquals(copy.value, 1); + Assert.assertEquals(copy.inner.value, 2); + Assert.assertSame(copy.inner.outer(), copy); + + InnerClassHolder.Inner innerRootCopy = serDe(fory, holder.inner); + Assert.assertEquals(innerRootCopy.value, 2); + Assert.assertEquals(innerRootCopy.outer().value, 1); + Assert.assertSame(innerRootCopy.outer().inner, innerRootCopy); + + Fory noRefFory = foryBuilder().withRefTracking(false).withCodegen(true).build(); + InnerClassHolder outer = new InnerClassHolder(); + outer.value = 3; + InnerClassHolder.Inner inner = outer.new Inner(); + inner.value = 5; + InnerClassHolder.Inner innerCopy = serDe(noRefFory, inner); + Assert.assertEquals(innerCopy.value, 5); + Assert.assertEquals(innerCopy.outer().value, 3); + Assert.assertNotSame(innerCopy.outer(), outer); + Assert.assertNull(innerCopy.outer().inner); + + // PriorityBlockingQueue holds an AbstractQueuedSynchronizer.ConditionObject inner instance. + PriorityBlockingQueue queue = + new PriorityBlockingQueue<>(4, Collections.reverseOrder()); + queue.addAll(Arrays.asList(1, 3, 2)); + PriorityBlockingQueue queueCopy = serDe(fory, queue); + Assert.assertEquals(queueCopy.poll(), Integer.valueOf(3)); + Assert.assertEquals(queueCopy.size(), 2); + } }