diff --git a/java/fory-core/src/main/java/org/apache/fory/BaseFory.java b/java/fory-core/src/main/java/org/apache/fory/BaseFory.java index c51ac2488c..a0012853ef 100644 --- a/java/fory-core/src/main/java/org/apache/fory/BaseFory.java +++ b/java/fory-core/src/main/java/org/apache/fory/BaseFory.java @@ -25,6 +25,7 @@ import org.apache.fory.io.ForyInputStream; import org.apache.fory.io.ForyReadableChannel; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.BufferCallback; import org.apache.fory.serializer.Serializer; @@ -234,6 +235,14 @@ void registerSerializerAndType( T deserialize(ForyReadableChannel channel, Class type); + T deserialize(byte[] bytes, TypeRef typeRef); + + T deserialize(MemoryBuffer buffer, TypeRef typeRef); + + T deserialize(ForyInputStream inputStream, TypeRef typeRef); + + T deserialize(ForyReadableChannel channel, TypeRef typeRef); + Object deserialize(byte[] bytes, Iterable outOfBandBuffers); /** Deserialize obj from a buffer. */ diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index a004eef527..936a6e5aa1 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -51,6 +51,7 @@ import org.apache.fory.logging.LoggerFactory; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.memory.MemoryUtils; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.ClassResolver; import org.apache.fory.resolver.SharedRegistry; import org.apache.fory.resolver.TypeChecker; @@ -462,6 +463,34 @@ public T deserialize(ForyReadableChannel channel, Class type) { return deserialize(channel.getBuffer(), type); } + @SuppressWarnings("unchecked") + @Override + public T deserialize(byte[] bytes, TypeRef typeRef) { + Preconditions.checkNotNull(typeRef, "typeRef must not be null"); + return deserialize(bytes, (Class) typeRef.getRawType()); + } + + @SuppressWarnings("unchecked") + @Override + public T deserialize(MemoryBuffer buffer, TypeRef typeRef) { + Preconditions.checkNotNull(typeRef, "typeRef must not be null"); + return deserialize(buffer, (Class) typeRef.getRawType()); + } + + @SuppressWarnings("unchecked") + @Override + public T deserialize(ForyInputStream inputStream, TypeRef typeRef) { + Preconditions.checkNotNull(typeRef, "typeRef must not be null"); + return deserialize(inputStream, (Class) typeRef.getRawType()); + } + + @SuppressWarnings("unchecked") + @Override + public T deserialize(ForyReadableChannel channel, TypeRef typeRef) { + Preconditions.checkNotNull(typeRef, "typeRef must not be null"); + return deserialize(channel, (Class) typeRef.getRawType()); + } + @Override public Object deserialize(byte[] bytes, Iterable outOfBandBuffers) { return deserialize(MemoryUtils.wrap(bytes), outOfBandBuffers); diff --git a/java/fory-core/src/main/java/org/apache/fory/ThreadLocalFory.java b/java/fory-core/src/main/java/org/apache/fory/ThreadLocalFory.java index 57f0d947dc..c3c8d3f5a4 100644 --- a/java/fory-core/src/main/java/org/apache/fory/ThreadLocalFory.java +++ b/java/fory-core/src/main/java/org/apache/fory/ThreadLocalFory.java @@ -33,6 +33,7 @@ import org.apache.fory.io.ForyInputStream; import org.apache.fory.io.ForyReadableChannel; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.SharedRegistry; import org.apache.fory.serializer.BufferCallback; @@ -146,6 +147,26 @@ public T deserialize(ForyReadableChannel channel, Class type) { return currentFory().deserialize(channel, type); } + @Override + public T deserialize(byte[] bytes, TypeRef typeRef) { + return currentFory().deserialize(bytes, typeRef); + } + + @Override + public T deserialize(MemoryBuffer buffer, TypeRef typeRef) { + return currentFory().deserialize(buffer, typeRef); + } + + @Override + public T deserialize(ForyInputStream inputStream, TypeRef typeRef) { + return currentFory().deserialize(inputStream, typeRef); + } + + @Override + public T deserialize(ForyReadableChannel channel, TypeRef typeRef) { + return currentFory().deserialize(channel, typeRef); + } + @Override public Object deserialize(byte[] bytes, Iterable outOfBandBuffers) { return currentFory().deserialize(bytes, outOfBandBuffers); diff --git a/java/fory-core/src/main/java/org/apache/fory/io/BlockedStreamUtils.java b/java/fory-core/src/main/java/org/apache/fory/io/BlockedStreamUtils.java index deb03160c2..0dbc52a474 100644 --- a/java/fory-core/src/main/java/org/apache/fory/io/BlockedStreamUtils.java +++ b/java/fory-core/src/main/java/org/apache/fory/io/BlockedStreamUtils.java @@ -29,6 +29,7 @@ import org.apache.fory.Fory; import org.apache.fory.exception.DeserializationException; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.serializer.BufferCallback; import org.apache.fory.util.ExceptionUtils; @@ -82,6 +83,16 @@ public static T deserialize(Fory fory, ReadableByteChannel channel, Class return type.cast(readFromChannel(fory, channel, b -> fory.deserialize(b, type))); } + @SuppressWarnings("unchecked") + public static T deserialize(Fory fory, InputStream inputStream, TypeRef typeRef) { + return (T) deserializeFromStream(fory, inputStream, buf -> fory.deserialize(buf, typeRef)); + } + + @SuppressWarnings("unchecked") + public static T deserialize(Fory fory, ReadableByteChannel channel, TypeRef typeRef) { + return (T) readFromChannel(fory, channel, b -> fory.deserialize(b, typeRef)); + } + private static Object readFromChannel( Fory fory, ReadableByteChannel channel, Function action) { try { diff --git a/java/fory-core/src/main/java/org/apache/fory/pool/ThreadPoolFory.java b/java/fory-core/src/main/java/org/apache/fory/pool/ThreadPoolFory.java index 04971ae20b..e125459053 100644 --- a/java/fory-core/src/main/java/org/apache/fory/pool/ThreadPoolFory.java +++ b/java/fory-core/src/main/java/org/apache/fory/pool/ThreadPoolFory.java @@ -35,6 +35,7 @@ import org.apache.fory.io.ForyInputStream; import org.apache.fory.io.ForyReadableChannel; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.SharedRegistry; import org.apache.fory.serializer.BufferCallback; @@ -274,6 +275,46 @@ public T deserialize(ForyReadableChannel channel, Class type) { } } + @Override + public T deserialize(byte[] bytes, TypeRef typeRef) { + PooledEntry entry = acquire(); + try { + return entry.fory.deserialize(bytes, typeRef); + } finally { + release(entry); + } + } + + @Override + public T deserialize(MemoryBuffer buffer, TypeRef typeRef) { + PooledEntry entry = acquire(); + try { + return entry.fory.deserialize(buffer, typeRef); + } finally { + release(entry); + } + } + + @Override + public T deserialize(ForyInputStream inputStream, TypeRef typeRef) { + PooledEntry entry = acquire(); + try { + return entry.fory.deserialize(inputStream, typeRef); + } finally { + release(entry); + } + } + + @Override + public T deserialize(ForyReadableChannel channel, TypeRef typeRef) { + PooledEntry entry = acquire(); + try { + return entry.fory.deserialize(channel, typeRef); + } finally { + release(entry); + } + } + @Override public Object deserialize(byte[] bytes, Iterable outOfBandBuffers) { PooledEntry entry = acquire(); diff --git a/java/fory-core/src/test/java/org/apache/fory/ForyTest.java b/java/fory-core/src/test/java/org/apache/fory/ForyTest.java index 62df5b3dc7..a41fe14a27 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ForyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ForyTest.java @@ -27,11 +27,13 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import java.io.ByteArrayInputStream; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.channels.Channels; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDate; @@ -45,6 +47,8 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; @@ -63,9 +67,12 @@ import org.apache.fory.exception.ForyException; import org.apache.fory.exception.InsecureException; import org.apache.fory.exception.SerializationException; +import org.apache.fory.io.ForyInputStream; +import org.apache.fory.io.ForyReadableChannel; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.memory.MemoryUtils; import org.apache.fory.reflect.ReflectionUtils; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.ArraySerializersTest; import org.apache.fory.serializer.EnumSerializerTest; @@ -980,4 +987,81 @@ public void testMaxDepthCodegen() { .build(); assertThrows(InsecureException.class, () -> fory.deserialize(bytes)); } + + @Test + public void testDeserializeWithTypeRef() throws Exception { + Fory fory = + Fory.builder() + .requireClassRegistration(false) + .withXlang(false) + .withCompatible(false) + .build(); + + // 1. Root ArrayList (exercises CollectionSerializer / CollectionLikeSerializer) + List arrayList = new ArrayList<>(Arrays.asList("hello", "fory", "world")); + byte[] listBytes = fory.serialize(arrayList); + TypeRef> listTypeRef = new TypeRef>() {}; + + assertEquals(fory.deserialize(listBytes, listTypeRef), arrayList); + assertEquals(fory.deserialize(MemoryBuffer.fromByteArray(listBytes), listTypeRef), arrayList); + + try (ForyInputStream stream = new ForyInputStream(new ByteArrayInputStream(listBytes))) { + assertEquals(fory.deserialize(stream, listTypeRef), arrayList); + } + try (ForyReadableChannel channel = + new ForyReadableChannel(Channels.newChannel(new ByteArrayInputStream(listBytes)))) { + assertEquals(fory.deserialize(channel, listTypeRef), arrayList); + } + + // 2. Root LinkedList and HashSet + List linkedList = new LinkedList<>(Arrays.asList("a", "b", "c")); + assertEquals( + fory.deserialize(fory.serialize(linkedList), new TypeRef>() {}), linkedList); + + Set set = new HashSet<>(Arrays.asList("x", "y", "z")); + assertEquals(fory.deserialize(fory.serialize(set), new TypeRef>() {}), set); + + // 3. Root Map and nested Map> + Map intMap = new HashMap<>(); + intMap.put("one", 1); + intMap.put("two", 2); + assertEquals( + fory.deserialize(fory.serialize(intMap), new TypeRef>() {}), intMap); + + Map> nestedMap = new HashMap<>(); + nestedMap.put("key1", new ArrayList<>(Arrays.asList(1, 2, 3))); + nestedMap.put("key2", new ArrayList<>(Arrays.asList(4, 5, 6))); + byte[] mapBytes = fory.serialize(nestedMap); + TypeRef>> mapTypeRef = new TypeRef>>() {}; + assertEquals(fory.deserialize(mapBytes, mapTypeRef), nestedMap); + assertEquals(fory.deserialize(MemoryBuffer.fromByteArray(mapBytes), mapTypeRef), nestedMap); + + // 4. Arrays.asList view + List asList = Arrays.asList("1", "2", "3"); + assertEquals(fory.deserialize(fory.serialize(asList), new TypeRef>() {}), asList); + + // 5. Concrete object with TypeRef.of + BeanA beanA = BeanA.createBeanA(2); + byte[] beanBytes = fory.serialize(beanA); + assertEquals(fory.deserialize(beanBytes, TypeRef.of(BeanA.class)), beanA); + + // 6. Null typeRef checks across all deserialize overloads + assertThrows( + NullPointerException.class, + () -> fory.deserialize(listBytes, (TypeRef>) null)); + assertThrows( + NullPointerException.class, + () -> + fory.deserialize(MemoryBuffer.fromByteArray(listBytes), (TypeRef>) null)); + try (ForyInputStream stream = new ForyInputStream(new ByteArrayInputStream(listBytes))) { + assertThrows( + NullPointerException.class, () -> fory.deserialize(stream, (TypeRef>) null)); + } + try (ForyReadableChannel channel = + new ForyReadableChannel(Channels.newChannel(new ByteArrayInputStream(listBytes)))) { + assertThrows( + NullPointerException.class, + () -> fory.deserialize(channel, (TypeRef>) null)); + } + } } diff --git a/java/fory-core/src/test/java/org/apache/fory/StreamTest.java b/java/fory-core/src/test/java/org/apache/fory/StreamTest.java index ec8ead87d3..701a1089f7 100644 --- a/java/fory-core/src/test/java/org/apache/fory/StreamTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/StreamTest.java @@ -44,6 +44,7 @@ import org.apache.fory.io.ForyStreamReader; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.reflect.ReflectionUtils; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.test.bean.BeanA; import org.testng.Assert; import org.testng.annotations.Test; @@ -272,6 +273,9 @@ public void testOutputStreamWithType() throws IOException { assertEquals(newObj, beanA); newObj = fory.deserialize(buf, BeanA.class); assertEquals(newObj, beanA); + buf.readerIndex(0); + newObj = fory.deserialize(buf, new TypeRef() {}); + assertEquals(newObj, beanA); } @Test @@ -311,6 +315,20 @@ public void testReadableChannel() throws IOException { Files.delete(tempFile); } } + { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + fory.serialize(bas, beanA); + + Path tempFile = Files.createTempFile("readable_channel_test", "data_3"); + Files.write(tempFile, bas.toByteArray()); + + try (ForyReadableChannel channel = of(Files.newByteChannel(tempFile))) { + Object newObj = fory.deserialize(channel, new TypeRef() {}); + assertEquals(newObj, beanA); + } finally { + Files.delete(tempFile); + } + } } @Test diff --git a/java/fory-core/src/test/java/org/apache/fory/ThreadSafeForyTest.java b/java/fory-core/src/test/java/org/apache/fory/ThreadSafeForyTest.java index 530a5429f3..ccacac14d0 100644 --- a/java/fory-core/src/test/java/org/apache/fory/ThreadSafeForyTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/ThreadSafeForyTest.java @@ -25,7 +25,14 @@ import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import java.io.ByteArrayInputStream; import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -38,8 +45,11 @@ import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; import org.apache.fory.exception.ForyException; +import org.apache.fory.io.ForyInputStream; +import org.apache.fory.io.ForyReadableChannel; import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.pool.ThreadPoolFory; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.resolver.SharedRegistry; import org.apache.fory.resolver.TypeResolver; import org.apache.fory.serializer.Serializer; @@ -425,6 +435,47 @@ public void testSerializeDeserializeWithType() { } } + @Test + public void testSerializeDeserializeWithTypeRef() throws Exception { + for (ThreadSafeFory fory : + new ThreadSafeFory[] { + Fory.builder() + .withXlang(false) + .requireClassRegistration(false) + .withCompatible(false) + .buildThreadSafeFory(), + Fory.builder() + .withXlang(false) + .requireClassRegistration(false) + .withCompatible(false) + .buildThreadSafeForyPool(2) + }) { + List list = new ArrayList<>(Arrays.asList("a", "b", "c")); + byte[] bytes = fory.serialize(list); + TypeRef> typeRef = new TypeRef>() {}; + Assert.assertEquals(fory.deserialize(bytes, typeRef), list); + MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32); + fory.serialize(buffer, list); + Assert.assertEquals(fory.deserialize(buffer, typeRef), list); + + try (ForyInputStream stream = new ForyInputStream(new ByteArrayInputStream(bytes))) { + Assert.assertEquals(fory.deserialize(stream, typeRef), list); + } + try (ForyReadableChannel channel = + new ForyReadableChannel(Channels.newChannel(new ByteArrayInputStream(bytes)))) { + Assert.assertEquals(fory.deserialize(channel, typeRef), list); + } + + Map map = new HashMap<>(); + map.put("k1", 1); + map.put("k2", 2); + byte[] mapBytes = fory.serialize(map); + TypeRef> mapTypeRef = new TypeRef>() {}; + Assert.assertEquals(fory.deserialize(mapBytes, mapTypeRef), map); + Assert.assertEquals(fory.deserialize(MemoryBuffer.fromByteArray(mapBytes), mapTypeRef), map); + } + } + @Test public void testByteBufferPositionLimit() { Fory writer = diff --git a/java/fory-core/src/test/java/org/apache/fory/io/BlockedStreamUtilsTest.java b/java/fory-core/src/test/java/org/apache/fory/io/BlockedStreamUtilsTest.java index 7e73c6a274..c5e5c29b19 100644 --- a/java/fory-core/src/test/java/org/apache/fory/io/BlockedStreamUtilsTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/io/BlockedStreamUtilsTest.java @@ -31,6 +31,7 @@ import org.apache.fory.ForyTestBase; import org.apache.fory.exception.DeserializationException; import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.reflect.TypeRef; import org.apache.fory.test.bean.Foo; import org.testng.annotations.Test; @@ -43,9 +44,11 @@ public void testDeserializeStream() { Foo foo = Foo.create(); BlockedStreamUtils.serialize(fory, stream, foo); BlockedStreamUtils.serialize(fory, stream, foo); + BlockedStreamUtils.serialize(fory, stream, foo); ByteArrayInputStream inputStream = new ByteArrayInputStream(stream.toByteArray()); assertEquals(BlockedStreamUtils.deserialize(fory, inputStream), foo); assertEquals(BlockedStreamUtils.deserialize(fory, inputStream, Foo.class), foo); + assertEquals(BlockedStreamUtils.deserialize(fory, inputStream, new TypeRef() {}), foo); } @Test @@ -55,10 +58,12 @@ public void testDeserializeChannel() { Foo foo = Foo.create(); BlockedStreamUtils.serialize(fory, stream, foo); BlockedStreamUtils.serialize(fory, stream, foo); + BlockedStreamUtils.serialize(fory, stream, foo); try (MemoryBufferReadableChannel channel = new MemoryBufferReadableChannel(MemoryBuffer.fromByteArray(stream.toByteArray()))) { assertEquals(BlockedStreamUtils.deserialize(fory, channel), foo); assertEquals(BlockedStreamUtils.deserialize(fory, channel, Foo.class), foo); + assertEquals(BlockedStreamUtils.deserialize(fory, channel, new TypeRef() {}), foo); } }