This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fury.git
The following commit(s) were added to refs/heads/main by this push:
new 3aeccf0a feat(java): support inconsistent registration by name/id
(#2120)
3aeccf0a is described below
commit 3aeccf0aed8d382deb767fb96984b40680ac3520
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Mar 25 21:21:15 2025 +0800
feat(java): support inconsistent registration by name/id (#2120)
## What does this PR do?
## Related issues
Closes #2119
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fury/issues/new/choose) describing the
need to do so and update the document if necessary.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
-->
---
.../src/main/java/org/apache/fury/Fury.java | 1 -
.../main/java/org/apache/fury/meta/ClassDef.java | 12 +-
.../java/org/apache/fury/meta/ClassDefDecoder.java | 19 ++-
.../java/org/apache/fury/meta/ClassDefEncoder.java | 15 +-
.../org/apache/fury/resolver/ClassResolver.java | 33 ++++
.../main/java/org/apache/fury/type/Descriptor.java | 2 +-
.../main/java/org/apache/fury/type/TypeUtils.java | 4 +
.../fury/serializer/MetaSharedCompatibleTest.java | 187 +++++++++++++++++----
8 files changed, 230 insertions(+), 43 deletions(-)
diff --git a/java/fury-core/src/main/java/org/apache/fury/Fury.java
b/java/fury-core/src/main/java/org/apache/fury/Fury.java
index 4127b516..e15ad6db 100644
--- a/java/fury-core/src/main/java/org/apache/fury/Fury.java
+++ b/java/fury-core/src/main/java/org/apache/fury/Fury.java
@@ -199,7 +199,6 @@ public final class Fury implements BaseFury {
/** register class with given type tag which will be used for cross-language
serialization. */
public void register(Class<?> cls, String typeName) {
- Preconditions.checkArgument(language != Language.JAVA);
int idx = typeName.lastIndexOf('.');
String namespace = "";
if (idx > 0) {
diff --git a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
index 212ddc29..a5d539c8 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java
@@ -238,8 +238,9 @@ public class ClassDef implements Serializable {
descriptorsMap.get(fieldInfo.getDefinedClass() + "." +
fieldInfo.getFieldName());
Descriptor newDesc = fieldInfo.toDescriptor(resolver);
Class<?> rawType = newDesc.getRawType();
- if (resolver.isRegistered(rawType)) {
- String typeAlias = resolver.getTypeAlias(rawType);
+ FieldType fieldType = fieldInfo.getFieldType();
+ if (fieldType instanceof RegisteredFieldType) {
+ String typeAlias = String.valueOf(((RegisteredFieldType)
fieldType).getClassId());
if (!typeAlias.equals(newDesc.getTypeName())) {
newDesc = newDesc.copyWithTypeName(typeAlias);
}
@@ -470,7 +471,12 @@ public class ClassDef implements Serializable {
@Override
public TypeRef<?> toTypeToken(ClassResolver classResolver) {
- return TypeRef.of(classResolver.getRegisteredClass(classId), new
TypeExtMeta(trackingRef));
+ Class<?> cls = classResolver.getRegisteredClass(classId);
+ if (cls == null) {
+ LOG.warn("Class {} not registered, take it as Struct type for
deserialization.", classId);
+ cls = NonexistentClass.NonexistentMetaShared.class;
+ }
+ return TypeRef.of(cls, new TypeExtMeta(trackingRef));
}
@Override
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
index 5567e2a6..4e693f35 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefDecoder.java
@@ -32,6 +32,7 @@ import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.meta.ClassDef.FieldType;
import org.apache.fury.meta.MetaString.Encoding;
import org.apache.fury.resolver.ClassResolver;
+import org.apache.fury.serializer.NonexistentClass;
import org.apache.fury.util.Preconditions;
/**
@@ -79,15 +80,25 @@ class ClassDefDecoder {
boolean isRegistered = (currentClassHeader & 0b1) != 0;
int numFields = currentClassHeader >>> 1;
if (isRegistered) {
- int registeredId = classDefBuf.readVarUint32Small7();
- Class<?> cls = classResolver.getClassInfo((short)
registeredId).getCls();
- className = cls.getName();
- classSpec = new ClassSpec(cls);
+ short registeredId = (short) classDefBuf.readVarUint32Small7();
+ if (classResolver.getRegisteredClass(registeredId) == null) {
+ classSpec = new
ClassSpec(NonexistentClass.NonexistentMetaShared.class);
+ className = classSpec.entireClassName;
+ } else {
+ Class<?> cls = classResolver.getClassInfo(registeredId).getCls();
+ className = cls.getName();
+ classSpec = new ClassSpec(cls);
+ }
} else {
String pkg = readPkgName(classDefBuf);
String typeName = readTypeName(classDefBuf);
classSpec = Encoders.decodePkgAndClass(pkg, typeName);
className = classSpec.entireClassName;
+ if (classResolver.isRegisteredByName(className)) {
+ Class<?> cls = classResolver.getRegisteredClass(className);
+ className = cls.getName();
+ classSpec = new ClassSpec(cls);
+ }
}
List<ClassDef.FieldInfo> fieldInfos = readFieldsInfo(classDefBuf,
className, numFields);
classFields.addAll(fieldInfos);
diff --git
a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
index 3ca0383a..1579b931 100644
--- a/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
+++ b/java/fury-core/src/main/java/org/apache/fury/meta/ClassDefEncoder.java
@@ -148,9 +148,18 @@ class ClassDefEncoder {
} else {
classDefBuf.writeVarUint32Small7(currentClassHeader);
Class<?> currentType = getType(type, className);
- Tuple2<String, String> encoded =
Encoders.encodePkgAndClass(currentType);
- writePkgName(classDefBuf, encoded.f0);
- writeTypeName(classDefBuf, encoded.f1);
+ String ns, typename;
+ if (classResolver.isRegisteredByName(type)) {
+ Tuple2<String, String> nameTuple =
classResolver.getRegisteredNameTuple(type);
+ ns = nameTuple.f0;
+ typename = nameTuple.f1;
+ } else {
+ Tuple2<String, String> encoded =
Encoders.encodePkgAndClass(currentType);
+ ns = encoded.f0;
+ typename = encoded.f1;
+ }
+ writePkgName(classDefBuf, ns);
+ writeTypeName(classDefBuf, typename);
}
writeFieldsInfo(classDefBuf, fields);
}
diff --git
a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
index 522bf310..2917b8a3 100644
--- a/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
+++ b/java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java
@@ -541,6 +541,28 @@ public class ClassResolver {
|| extRegistry.registeredClasses.inverse().containsKey(cls);
}
+ public boolean isRegisteredByName(String name) {
+ return extRegistry.registeredClasses.containsKey(name);
+ }
+
+ public boolean isRegisteredByName(Class<?> cls) {
+ return extRegistry.registeredClasses.inverse().containsKey(cls);
+ }
+
+ public String getRegisteredName(Class<?> cls) {
+ return extRegistry.registeredClasses.inverse().get(cls);
+ }
+
+ public Tuple2<String, String> getRegisteredNameTuple(Class<?> cls) {
+ String name = extRegistry.registeredClasses.inverse().get(cls);
+ int index = name.lastIndexOf(".");
+ if (index != -1) {
+ return Tuple2.of(name.substring(0, index), name.substring(index + 1));
+ } else {
+ return Tuple2.of("", name);
+ }
+ }
+
public boolean isRegisteredById(Class<?> cls) {
return extRegistry.registeredClassIdMap.get(cls) != null;
}
@@ -559,6 +581,10 @@ public class ClassResolver {
return null;
}
+ public Class<?> getRegisteredClass(String className) {
+ return extRegistry.registeredClasses.get(className);
+ }
+
public List<Class<?>> getRegisteredClasses() {
return Arrays.stream(registeredId2ClassInfo)
.filter(Objects::nonNull)
@@ -1023,6 +1049,9 @@ public class ClassResolver {
}
public boolean isMap(Class<?> cls) {
+ if (cls == NonexistentMetaShared.class) {
+ return false;
+ }
return Map.class.isAssignableFrom(cls)
|| (fury.getConfig().isScalaOptimizationEnabled()
&& ScalaTypes.getScalaMapType().isAssignableFrom(cls));
@@ -1893,6 +1922,10 @@ public class ClassResolver {
private Class<?> loadClass(
String className, boolean isEnum, int arrayDims, boolean
deserializeNonexistentClass) {
extRegistry.classChecker.checkClass(this, className);
+ Class<?> cls = extRegistry.registeredClasses.get(className);
+ if (cls != null) {
+ return cls;
+ }
try {
return Class.forName(className, false, fury.getClassLoader());
} catch (ClassNotFoundException e) {
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
index 7b69d024..25ba210d 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/Descriptor.java
@@ -206,7 +206,7 @@ public class Descriptor {
sb.append(", name=").append(name);
sb.append(", modifier=").append(modifier);
if (field != null) {
- sb.append(",
field=").append(field.getDeclaringClass().getSimpleName()).append('.');
+ sb.append(",
declaringClass=").append(field.getDeclaringClass().getSimpleName());
}
if (readMethod != null) {
sb.append(", readMethod=").append(readMethod);
diff --git a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
index bb9f4ae2..3840c3ff 100644
--- a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
+++ b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java
@@ -50,6 +50,7 @@ import org.apache.fury.collection.Tuple2;
import org.apache.fury.reflect.ReflectionUtils;
import org.apache.fury.reflect.TypeParameter;
import org.apache.fury.reflect.TypeRef;
+import org.apache.fury.serializer.NonexistentClass;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.StringUtils;
@@ -553,6 +554,9 @@ public class TypeUtils {
}
public static boolean isMap(Class<?> cls) {
+ if (cls == NonexistentClass.NonexistentMetaShared.class) {
+ return false;
+ }
return cls == HashMap.class || Map.class.isAssignableFrom(cls);
}
diff --git
a/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
b/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
index 60e15e8b..ecd19d78 100644
---
a/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
+++
b/java/fury-core/src/test/java/org/apache/fury/serializer/MetaSharedCompatibleTest.java
@@ -19,6 +19,7 @@
package org.apache.fury.serializer;
+import static org.apache.fury.reflect.ReflectionUtils.getObjectFieldValue;
import static org.apache.fury.serializer.ClassUtils.loadClass;
import com.google.common.collect.ImmutableSet;
@@ -54,7 +55,7 @@ import org.testng.annotations.Test;
* interoperability between them.
*/
public class MetaSharedCompatibleTest extends FuryTestBase {
- public static Object serDeCheck(Fury fury, Object obj) {
+ public static Object serDeMetaSharedCheck(Fury fury, Object obj) {
Object newObj = serDeMetaShared(fury, obj);
Assert.assertEquals(newObj, obj);
return newObj;
@@ -115,9 +116,9 @@ public class MetaSharedCompatibleTest extends FuryTestBase {
.withRefTracking(referenceTracking)
.withCodegen(enableCodegen)
.build();
- serDeCheck(fury, Foo.create());
- serDeCheck(fury, BeanB.createBeanB(2));
- serDeCheck(fury, BeanA.createBeanA(2));
+ serDeMetaSharedCheck(fury, Foo.create());
+ serDeMetaSharedCheck(fury, BeanB.createBeanB(2));
+ serDeMetaSharedCheck(fury, BeanA.createBeanA(2));
}
@Test(dataProvider = "config2")
@@ -673,40 +674,82 @@ public class MetaSharedCompatibleTest extends
FuryTestBase {
new ClassDefEncoderTest
.TestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLengthTestClassLength
.InnerClassTestLengthInnerClassTestLengthInnerClassTestLength();
- serDeCheck(fury, o);
+ serDeMetaSharedCheck(fury, o);
}
+ CompileUnit aunit =
+ new CompileUnit(
+ "demo.pkg1",
+ "A",
+ (""
+ + "package demo.pkg1;\n"
+ + "import demo.test.*;\n"
+ + "import demo.report.*;\n"
+ + "public class A {\n"
+ + " int f1;\n"
+ + " B f2;\n"
+ + " C f3;\n"
+ + " public String toString() {return \"A\" + \",\" + f1 + \",\"
+ f2 + \",\" + f3;}\n"
+ + " public static A create() { A a = new A(); a.f1 = 10; a.f2 =
new B(); a.f3 = new C(); return a;}\n"
+ + "}"));
+
+ CompileUnit bunit =
+ new CompileUnit(
+ "demo.test",
+ "B",
+ ("" + "package demo.test;\n" + "public class B {\n" + " public int
f1 = 100;\n" + "}"));
+
+ CompileUnit cunit =
+ new CompileUnit(
+ "demo.report",
+ "C",
+ (""
+ + "package demo.report;\n"
+ + "public class C {\n"
+ + " public int f2 = 1000;\n"
+ + " public String toString() {return \"C{f2=\" + f2 + \"}\";}\n"
+ + "}"));
+
+ CompileUnit newAUnit =
+ new CompileUnit(
+ "example.pkg1",
+ "A",
+ (""
+ + "package example.pkg1;\n"
+ + "import example.test.*;\n"
+ + "import example.report.*;\n"
+ + "public class A {\n"
+ + " public int f1;\n"
+ + " public C f3;\n"
+ + " public String toString() {return \"A\" + \",\" + f1 + \",\"
+ f3;}\n"
+ + " public static A create() { A a = new A(); a.f1 = 10; a.f3 =
new C(); return a;}\n"
+ + "}"));
+
+ CompileUnit newCUnit =
+ new CompileUnit(
+ "example.test",
+ "C",
+ (""
+ + "package example.test;\n"
+ + "public class C {\n"
+ + " public int f2;\n"
+ + " public String toString() {return \"C{f2=\" + f2 + \"}\";}\n"
+ + "}"));
+
@Test
public void testRegisterToSameIdForRenamedClass() throws Exception {
- CompileUnit unit1 =
- new CompileUnit(
- "demo.pkg1",
- "A",
- (""
- + "package demo.pkg1;\n"
- + "import demo.test.*;\n"
- + "import demo.report.*;\n"
- + "public class A {\n"
- + " int f1;\n"
- + " B f2;\n"
- + " C f3;\n"
- + " public String toString() {return \"A\" + \",\" + f1 +
\",\" + f2 + \",\" + f3;}\n"
- + " public static A create() { A a = new A(); a.f1 = 10; a.f2
= new B(); a.f3 = new C(); return a;}\n"
- + "}"));
- CompileUnit unit2 =
- new CompileUnit(
- "demo.test", "B", ("" + "package demo.test;\n" + "public class B
{\n" + "}"));
- CompileUnit unit3 =
- new CompileUnit(
- "demo.report", "C", ("" + "package demo.report;\n" + "public class
C {\n" + "}"));
ClassLoader classLoader =
- JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
unit1, unit2, unit3);
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
aunit, bunit, cunit);
byte[] serialized;
{
Class<?> A = classLoader.loadClass("demo.pkg1.A");
Class<?> B = classLoader.loadClass("demo.test.B");
Class<?> C = classLoader.loadClass("demo.report.C");
- Fury fury =
builder().withCompatibleMode(CompatibleMode.COMPATIBLE).build();
+ Fury fury =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
fury.register(A);
fury.register(B);
fury.register(C);
@@ -715,7 +758,7 @@ public class MetaSharedCompatibleTest extends FuryTestBase {
serialized = fury.serialize(a);
}
{
- unit1 =
+ CompileUnit unit1 =
new CompileUnit(
"example.pkg1",
"A",
@@ -730,12 +773,12 @@ public class MetaSharedCompatibleTest extends
FuryTestBase {
+ " public String toString() {return \"A\" + \",\" + f1 +
\",\" + f2 + \",\" + f3;}\n"
+ " public static A create() { A a = new A(); a.f1 = 10;
a.f2 = new B(); a.f3 = new C(); return a;}\n"
+ "}"));
- unit2 =
+ CompileUnit unit2 =
new CompileUnit(
"example.report",
"B",
("" + "package example.report;\n" + "public class B {\n" + "}"));
- unit3 =
+ CompileUnit unit3 =
new CompileUnit(
"example.test", "C", ("" + "package example.test;\n" + "public
class C {\n" + "}"));
classLoader =
@@ -755,4 +798,86 @@ public class MetaSharedCompatibleTest extends FuryTestBase
{
System.out.println(newObj);
}
}
+
+ @Test
+ public void testInconsistentRegistrationID() throws Exception {
+ ClassLoader classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
aunit, bunit, cunit);
+ byte[] serialized;
+ {
+ Class<?> A = classLoader.loadClass("demo.pkg1.A");
+ Class<?> B = classLoader.loadClass("demo.test.B");
+ Class<?> C = classLoader.loadClass("demo.report.C");
+ Fury fury =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ fury.register(A, 300);
+ fury.register(B, 301);
+ fury.register(C, 302);
+ Object a = A.getMethod("create").invoke(null);
+ System.out.println(a);
+ serialized = fury.serialize(a);
+ }
+ {
+ classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
newAUnit, newCUnit);
+ Class<?> A = classLoader.loadClass("example.pkg1.A");
+ Class<?> C = classLoader.loadClass("example.test.C");
+ Fury fury =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ fury.register(A, 300);
+ fury.register(C, 302);
+ Object newObj = fury.deserialize(serialized);
+ System.out.println(newObj);
+ Object f3 = getObjectFieldValue(newObj, "f3");
+ Assert.assertNotNull(f3);
+ Assert.assertEquals(f3.toString(), "C{f2=1000}");
+ }
+ }
+
+ @Test
+ public void testInconsistentRegistrationName() throws Exception {
+ ClassLoader classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
aunit, bunit, cunit);
+ byte[] serialized;
+ {
+ Class<?> A = classLoader.loadClass("demo.pkg1.A");
+ Class<?> B = classLoader.loadClass("demo.test.B");
+ Class<?> C = classLoader.loadClass("demo.report.C");
+ Fury fury =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ fury.register(A, "test.A");
+ fury.register(B, "test.B");
+ fury.register(C, "test.C");
+ Object a = A.getMethod("create").invoke(null);
+ System.out.println(a);
+ serialized = fury.serialize(a);
+ }
+ {
+ classLoader =
+ JaninoUtils.compile(Thread.currentThread().getContextClassLoader(),
newAUnit, newCUnit);
+ Class<?> A = classLoader.loadClass("example.pkg1.A");
+ Class<?> C = classLoader.loadClass("example.test.C");
+ Fury fury =
+ builder()
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withClassLoader(classLoader)
+ .build();
+ fury.register(A, "test.A");
+ fury.register(C, "test.C");
+ Object newObj = fury.deserialize(serialized);
+ System.out.println(newObj);
+ Object f3 = getObjectFieldValue(newObj, "f3");
+ Assert.assertNotNull(f3);
+ Assert.assertEquals(f3.toString(), "C{f2=1000}");
+ }
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]