diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java index e1b6b30e04..a095bf465f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java @@ -221,6 +221,10 @@ public String codecQualifiedClassName(Class beanClass) { protected abstract String codecSuffix(); + protected abstract Expression buildXlangDecodeExpression(); + + protected abstract Expression buildXlangEncodeExpression(); + protected T fory(Function function) { return fory.getJITContext().asyncVisitFory(function); } @@ -238,8 +242,7 @@ public String genCode() { ctx.extendsClasses(ctx.type(parentSerializerClass)); ctx.reserveName(POJO_CLASS_TYPE_NAME); ctx.addField(ctx.type(Fory.class), FORY_NAME); - Expression encodeExpr = buildEncodeExpression(); - Expression decodeExpr = buildDecodeExpression(); + boolean xlang = fory.isCrossLanguage(); String constructorCode = StringUtils.format( "" @@ -250,22 +253,43 @@ public String genCode() { FORY_NAME, "cls", POJO_CLASS_TYPE_NAME); - - ctx.clearExprState(); - String encodeCode = encodeExpr.genCode(ctx).code(); - encodeCode = ctx.optimizeMethodCode(encodeCode); - ctx.clearExprState(); - String decodeCode = decodeExpr.genCode(ctx).code(); - decodeCode = ctx.optimizeMethodCode(decodeCode); - ctx.overrideMethod( - "write", - encodeCode, - void.class, - MemoryBuffer.class, - BUFFER_NAME, - Object.class, - ROOT_OBJECT_NAME); - ctx.overrideMethod("read", decodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + if (!xlang) { + Expression encodeExpr = buildEncodeExpression(); + Expression decodeExpr = buildDecodeExpression(); + ctx.clearExprState(); + String encodeCode = encodeExpr.genCode(ctx).code(); + encodeCode = ctx.optimizeMethodCode(encodeCode); + ctx.clearExprState(); + String decodeCode = decodeExpr.genCode(ctx).code(); + decodeCode = ctx.optimizeMethodCode(decodeCode); + ctx.overrideMethod( + "write", + encodeCode, + void.class, + MemoryBuffer.class, + BUFFER_NAME, + Object.class, + ROOT_OBJECT_NAME); + ctx.overrideMethod("read", decodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + } else { + Expression xlangEncodeExpr = buildXlangEncodeExpression(); + Expression xlangDecodeExpr = buildXlangDecodeExpression(); + ctx.clearExprState(); + String xlangEncodeCode = xlangEncodeExpr.genCode(ctx).code(); + xlangEncodeCode = ctx.optimizeMethodCode(xlangEncodeCode); + ctx.clearExprState(); + String xlangDecodeCode = xlangDecodeExpr.genCode(ctx).code(); + xlangDecodeCode = ctx.optimizeMethodCode(xlangDecodeCode); + ctx.overrideMethod( + "xwrite", + xlangEncodeCode, + void.class, + MemoryBuffer.class, + BUFFER_NAME, + Object.class, + ROOT_OBJECT_NAME); + ctx.overrideMethod("xread", xlangDecodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + } registerJITNotifyCallback(); ctx.addConstructor(constructorCode, Fory.class, "fory", Class.class, POJO_CLASS_TYPE_NAME); return ctx.genCode(); diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/CompatibleCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/CompatibleCodecBuilder.java index 6a1342a526..7490623eab 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/CompatibleCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/CompatibleCodecBuilder.java @@ -337,6 +337,11 @@ public Expression buildEncodeExpression() { return expressions; } + @Override + public Expression buildXlangEncodeExpression() { + throw new IllegalStateException("unreachable"); + } + private Expression writeEmbedTypeFieldValue( Expression bean, Expression buffer, FieldInfo fieldInfo) { Descriptor descriptor = createDescriptor(fieldInfo); @@ -381,6 +386,11 @@ public Expression buildDecodeExpression() { return expressionBuilder; } + @Override + public Expression buildXlangDecodeExpression() { + throw new IllegalStateException("unreachable"); + } + public Expression buildRecordDecodeExpression() { Reference buffer = new Reference(BUFFER_NAME, bufferTypeRef, false); StaticInvoke components = diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java index 099b0081dd..65cc14d699 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/MetaSharedCodecBuilder.java @@ -126,11 +126,19 @@ public String genCode() { MetaSharedCodecBuilder.class.getName(), "serializer", SERIALIZER_FIELD_NAME); + boolean xlang = fory.isCrossLanguage(); ctx.clearExprState(); - Expression decodeExpr = buildDecodeExpression(); - String decodeCode = decodeExpr.genCode(ctx).code(); - decodeCode = ctx.optimizeMethodCode(decodeCode); - ctx.overrideMethod("read", decodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + if (!xlang) { + Expression decodeExpr = buildDecodeExpression(); + String decodeCode = decodeExpr.genCode(ctx).code(); + decodeCode = ctx.optimizeMethodCode(decodeCode); + ctx.overrideMethod("read", decodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + } else { + Expression xlangDecodeExpression = buildXlangDecodeExpression(); + String xlangDecodeCode = xlangDecodeExpression.genCode(ctx).code(); + xlangDecodeCode = ctx.optimizeMethodCode(xlangDecodeCode); + ctx.overrideMethod("xread", xlangDecodeCode, Object.class, MemoryBuffer.class, BUFFER_NAME); + } registerJITNotifyCallback(); ctx.addConstructor(constructorCode, Fory.class, "fory", Class.class, POJO_CLASS_TYPE_NAME); return ctx.genCode(); diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java index b4e525d7f6..e6bff1a1d9 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java @@ -175,6 +175,11 @@ public Expression buildEncodeExpression() { return expressions; } + @Override + public Expression buildXlangEncodeExpression() { + return buildEncodeExpression(); + } + private void addGroupExpressions( List> writeGroup, int numGroups, @@ -485,6 +490,11 @@ public Expression buildDecodeExpression() { return expressions; } + @Override + public Expression buildXlangDecodeExpression() { + return buildDecodeExpression(); + } + private void deserializeReadGroup( List> readGroups, int numGroups, diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfo.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfo.java index ef6b154b07..cdde2915a4 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfo.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfo.java @@ -121,6 +121,22 @@ public class ClassInfo { } } + public static ClassInfo copy(ClassInfo classInfo) { + final ClassInfo copy = + new ClassInfo( + classInfo.cls, + classInfo.fullNameBytes, + classInfo.namespaceBytes, + classInfo.typeNameBytes, + classInfo.isDynamicGeneratedClass, + classInfo.serializer, + classInfo.classId, + (short) classInfo.xtypeId); + copy.needToWriteClassDef = classInfo.needToWriteClassDef; + copy.classDef = classInfo.classDef; + return copy; + } + public Class getCls() { return cls; } diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java index 28e25d8db9..95064e6e32 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java @@ -25,7 +25,11 @@ import static org.apache.fory.meta.Encoders.PACKAGE_DECODER; import static org.apache.fory.meta.Encoders.PACKAGE_ENCODER; import static org.apache.fory.meta.Encoders.TYPE_NAME_DECODER; +import static org.apache.fory.resolver.ClassResolver.NIL_CLASS_INFO; import static org.apache.fory.resolver.ClassResolver.NO_CLASS_ID; +import static org.apache.fory.serializer.CodegenSerializer.loadCodegenSerializer; +import static org.apache.fory.serializer.CodegenSerializer.loadCompatibleCodegenSerializer; +import static org.apache.fory.serializer.CodegenSerializer.supportCodegenForJavaSerialization; import static org.apache.fory.serializer.collection.MapSerializers.HashMapSerializer; import static org.apache.fory.type.TypeUtils.qualifiedName; @@ -49,6 +53,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.fory.Fory; +import org.apache.fory.builder.JITContext; import org.apache.fory.collection.IdentityMap; import org.apache.fory.collection.IdentityObjectIntMap; import org.apache.fory.collection.LongMap; @@ -66,8 +71,11 @@ import org.apache.fory.reflect.ReflectionUtils; import org.apache.fory.reflect.TypeRef; import org.apache.fory.serializer.ArraySerializers; +import org.apache.fory.serializer.CodegenSerializer; +import org.apache.fory.serializer.CompatibleSerializer; import org.apache.fory.serializer.EnumSerializer; import org.apache.fory.serializer.LazySerializer; +import org.apache.fory.serializer.MetaSharedSerializer; import org.apache.fory.serializer.NonexistentClass; import org.apache.fory.serializer.NonexistentClassSerializers; import org.apache.fory.serializer.ObjectSerializer; @@ -98,7 +106,7 @@ public class XtypeResolver implements TypeResolver { private final Config config; private final Fory fory; private final ClassResolver classResolver; - private final ClassInfoHolder classInfoCache = new ClassInfoHolder(ClassResolver.NIL_CLASS_INFO); + private final ClassInfoHolder classInfoCache = new ClassInfoHolder(NIL_CLASS_INFO); private final MetaStringResolver metaStringResolver; // IdentityMap has better lookup performance, when loadFactor is 0.05f, performance is better private final IdentityMap, ClassInfo> classInfoMap = new IdentityMap<>(64, loadFactor); @@ -109,6 +117,7 @@ public class XtypeResolver implements TypeResolver { new ObjectMap<>(16, loadFactor); private final Map, ClassDef> classDefMap = new HashMap<>(); private final boolean shareMeta; + private final Set> classCtx = new HashSet<>(); private int xtypeIdGenerator = 64; // Use ClassInfo[] or LongMap? @@ -242,6 +251,70 @@ private void register( xtypeIdToClassMap.put(xtypeId, classInfo); } + private void addSerializer(Class type, ClassInfo classInfo) { + classInfoMap.put(type, classInfo); + xtypeIdToClassMap.put(classInfo.xtypeId, classInfo); + qualifiedType2ClassInfo.put(qualifiedName(classInfo.decodeTypeName(), classInfo.decodeTypeName()), classInfo); + + } + + private Class getObjectSerializerClass( + Class cls, + boolean shareMeta, + boolean codegen, + JITContext.SerializerJITCallback> callback) { + if (codegen) { + if (classCtx.contains(cls)) { + // avoid potential recursive call for seq codec generation. + return CodegenSerializer.LazyInitBeanSerializer.class; + } else { + try { + classCtx.add(cls); + Class sc; + switch (fory.getCompatibleMode()) { + case SCHEMA_CONSISTENT: + sc = + fory.getJITContext() + .registerSerializerJITCallback( + () -> ObjectSerializer.class, + () -> loadCodegenSerializer(fory, cls), + callback); + return sc; + case COMPATIBLE: + sc = + fory.getJITContext() + .registerSerializerJITCallback( + () -> shareMeta ? ObjectSerializer.class : CompatibleSerializer.class, + () -> + shareMeta + ? loadCodegenSerializer(fory, cls) + : loadCompatibleCodegenSerializer(fory, cls), + callback); + return sc; + default: + throw new UnsupportedOperationException( + String.format("Unsupported mode %s", fory.getCompatibleMode())); + } + } finally { + classCtx.remove(cls); + } + } + } else { + if (codegen) { + LOG.info("Object of type {} can't be serialized by jit", cls); + } + switch (fory.getCompatibleMode()) { + case SCHEMA_CONSISTENT: + return ObjectSerializer.class; + case COMPATIBLE: + return shareMeta ? ObjectSerializer.class : MetaSharedSerializer.class; + default: + throw new UnsupportedOperationException( + String.format("Unsupported mode %s", fory.getCompatibleMode())); + } + } + } + private boolean isStructType(Serializer serializer) { if (serializer instanceof ObjectSerializer || serializer instanceof GeneratedSerializer) { return true; @@ -363,6 +436,9 @@ public ClassInfo getClassInfo(Class cls, boolean createIfAbsent) { if (createIfAbsent) { return getClassInfo(cls); } + if (classCtx.contains(cls)) { + return null; + } return classInfoMap.get(cls); } @@ -580,7 +656,36 @@ private ClassDef buildClassDef(ClassInfo classInfo) { @Override public Serializer getSerializer(Class cls) { - return (Serializer) getClassInfo(cls).serializer; + ClassInfo classInfo = getClassInfo(cls); + if (classInfo.serializer instanceof LazySerializer.LazyObjectSerializer) { + boolean codegen = + supportCodegenForJavaSerialization(cls) && fory.getConfig().isCodeGenEnabled(); + Class objectSerializerClass = + getObjectSerializerClass( + cls, + shareMeta, + codegen, + new JITContext.SerializerJITCallback>() { + @Override + public void onSuccess(Class result) { + Serializer objectSerializer = Serializers.newSerializer(fory, cls, result); + ClassInfo copy = ClassInfo.copy(classInfo); + copy.serializer = objectSerializer; + addSerializer(cls, copy); + if (classInfoCache.classInfo.cls == cls) { + classInfoCache.classInfo = NIL_CLASS_INFO; // clear class info cache + } + Preconditions.checkState(getSerializer(cls).getClass() == result); + } + + @Override + public Object id() { + return cls; + } + }); + classInfo.serializer = Serializers.newSerializer(fory, cls, objectSerializerClass); + } + return (Serializer) classInfo.serializer; } @Override diff --git a/java/fory-core/src/test/java/org/apache/fory/xlang/CodegenXlangTest.java b/java/fory-core/src/test/java/org/apache/fory/xlang/CodegenXlangTest.java new file mode 100644 index 0000000000..1226a28fdf --- /dev/null +++ b/java/fory-core/src/test/java/org/apache/fory/xlang/CodegenXlangTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fory.xlang; + +import org.apache.fory.CrossLanguageTest; +import org.apache.fory.Fory; +import org.apache.fory.ForyTestBase; +import org.apache.fory.config.CompatibleMode; +import org.apache.fory.config.Language; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class CodegenXlangTest extends ForyTestBase { + + @Test + public void testCodeGenSchemaConsistent() { + Fory fory = + Fory.builder() + .withCodegen(true) + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .requireClassRegistration(true) + .requireClassRegistration(false) + .build(); + Fory fory1 = + Fory.builder() + .withCodegen(false) + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .requireClassRegistration(true) + .requireClassRegistration(false) + .build(); + // fory.register(CrossLanguageTest.Foo.class, "example.foo"); + // fory.register(CrossLanguageTest.Bar.class, "example.bar"); + // fory1.register(CrossLanguageTest.Foo.class, "example.foo"); + // fory1.register(CrossLanguageTest.Bar.class, "example.bar"); + + serDeCheck(fory, CrossLanguageTest.Bar.create()); + serDeCheck(fory, CrossLanguageTest.Foo.create()); + serDeCheck(fory1, CrossLanguageTest.Bar.create()); + serDeCheck(fory1, CrossLanguageTest.Foo.create()); + CrossLanguageTest.Bar bar = CrossLanguageTest.Bar.create(); + byte[] serialize = fory.serialize(bar); + Object deserialize = fory1.deserialize(serialize); + Assert.assertEquals(bar, deserialize); + byte[] serialize1 = fory1.serialize(bar); + Object deserialize1 = fory1.deserialize(serialize1); + Assert.assertEquals(bar, deserialize1); + } + + @Test + public void testCodeGenCompatible() { + Fory fory = + Fory.builder() + .withCodegen(true) + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .requireClassRegistration(true) + .build(); + Fory fory1 = + Fory.builder() + .withCodegen(false) + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .requireClassRegistration(true) + .build(); + fory.register(CrossLanguageTest.Foo.class, "example.foo"); + fory.register(CrossLanguageTest.Bar.class, "example.bar"); + fory1.register(CrossLanguageTest.Foo.class, "example.foo"); + fory1.register(CrossLanguageTest.Bar.class, "example.bar"); + serDeCheck(fory, CrossLanguageTest.Bar.create()); + serDeCheck(fory, CrossLanguageTest.Foo.create()); + serDeCheck(fory1, CrossLanguageTest.Bar.create()); + serDeCheck(fory1, CrossLanguageTest.Foo.create()); + CrossLanguageTest.Bar bar = CrossLanguageTest.Bar.create(); + byte[] serialize = fory.serialize(bar); + Object deserialize = fory1.deserialize(serialize); + Assert.assertEquals(bar, deserialize); + byte[] serialize1 = fory1.serialize(bar); + Object deserialize1 = fory1.deserialize(serialize1); + Assert.assertEquals(bar, deserialize1); + } +}