blob: c16df6d02e888d82c82bb51de1a7cc522df33045 [file] [log] [blame]
/*
* 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.fury.builder;
import static org.apache.fury.builder.Generated.GeneratedMetaSharedSerializer.SERIALIZER_FIELD_NAME;
import com.google.common.reflect.TypeToken;
import java.util.Collection;
import java.util.SortedMap;
import org.apache.fury.Fury;
import org.apache.fury.builder.Generated.GeneratedMetaSharedSerializer;
import org.apache.fury.codegen.CodeGenerator;
import org.apache.fury.codegen.Expression;
import org.apache.fury.codegen.Expression.Literal;
import org.apache.fury.config.CompatibleMode;
import org.apache.fury.config.FuryBuilder;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.meta.ClassDef;
import org.apache.fury.serializer.CodegenSerializer;
import org.apache.fury.serializer.MetaSharedSerializer;
import org.apache.fury.serializer.ObjectSerializer;
import org.apache.fury.serializer.Serializer;
import org.apache.fury.serializer.Serializers;
import org.apache.fury.type.Descriptor;
import org.apache.fury.type.DescriptorGrouper;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.StringUtils;
import org.apache.fury.util.Utils;
import org.apache.fury.util.record.RecordComponent;
import org.apache.fury.util.record.RecordUtils;
/**
* A meta-shared compatible deserializer builder based on {@link ClassDef}. This builder will
* compare fields between {@link ClassDef} and class fields, then create serializer to read and
* set/skip corresponding fields to support type forward/backward compatibility. Serializer are
* forward to {@link ObjectCodecBuilder} for now. We can consolidate fields between peers to create
* better serializers to serialize common fields between peers for efficiency.
*
* <p>With meta context share enabled and compatible mode, the {@link ObjectCodecBuilder} will take
* all non-inner final types as non-final, so that fury can write class definition when write class
* info for those types.
*
* @see CompatibleMode
* @see FuryBuilder#withMetaContextShare
* @see GeneratedMetaSharedSerializer
* @see MetaSharedSerializer
*/
public class MetaSharedCodecBuilder extends ObjectCodecBuilder {
private final ClassDef classDef;
public MetaSharedCodecBuilder(TypeToken<?> beanType, Fury fury, ClassDef classDef) {
super(beanType, fury, GeneratedMetaSharedSerializer.class);
Preconditions.checkArgument(
!fury.getConfig().checkClassVersion(),
"Class version check should be disabled when compatible mode is enabled.");
this.classDef = classDef;
Collection<Descriptor> descriptors =
visitFury(
f -> MetaSharedSerializer.consolidateFields(f.getClassResolver(), beanClass, classDef));
DescriptorGrouper grouper =
DescriptorGrouper.createDescriptorGrouper(
descriptors, true, fury.compressInt(), fury.compressLong());
objectCodecOptimizer =
new ObjectCodecOptimizer(beanClass, grouper, !fury.isBasicTypesRefIgnored(), ctx);
}
@Override
protected String codecSuffix() {
// For every class def sent from different peer, if the class def are different, then
// a new serializer needs being generated.
return "MetaShared" + classDef.getId();
}
@Override
public String genCode() {
ctx.setPackage(CodeGenerator.getPackage(beanClass));
String className = codecClassName(beanClass);
ctx.setClassName(className);
// don't addImport(beanClass), because user class may name collide.
ctx.extendsClasses(ctx.type(parentSerializerClass));
ctx.reserveName(POJO_CLASS_TYPE_NAME);
ctx.reserveName(SERIALIZER_FIELD_NAME);
ctx.addField(ctx.type(Fury.class), FURY_NAME);
String constructorCode =
StringUtils.format(
""
+ "super(${fury}, ${cls});\n"
+ "this.${fury} = ${fury};\n"
+ "${serializer} = ${builderClass}.setCodegenSerializer(${fury}, ${cls}, this);\n",
"fury",
FURY_NAME,
"cls",
POJO_CLASS_TYPE_NAME,
"builderClass",
MetaSharedCodecBuilder.class.getName(),
"serializer",
SERIALIZER_FIELD_NAME);
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);
registerJITNotifyCallback();
ctx.addConstructor(constructorCode, Fury.class, "fury", Class.class, POJO_CLASS_TYPE_NAME);
return ctx.genCode();
}
@Override
protected void addCommonImports() {
super.addCommonImports();
ctx.addImport(GeneratedMetaSharedSerializer.class);
}
// Invoked by JIT.
@SuppressWarnings({"unchecked", "rawtypes"})
public static Serializer setCodegenSerializer(
Fury fury, Class<?> cls, GeneratedMetaSharedSerializer s) {
// This method hold jit lock, so create jit serializer async to avoid block serialization.
Class serializerClass =
fury.getJITContext()
.registerSerializerJITCallback(
() -> ObjectSerializer.class,
() -> CodegenSerializer.loadCodegenSerializer(fury, s.getType()),
c -> s.serializer = Serializers.newSerializer(fury, s.getType(), c));
return Serializers.newSerializer(fury, cls, serializerClass);
}
@Override
public Expression buildEncodeExpression() {
throw new IllegalStateException("unreachable");
}
@Override
protected Expression buildComponentsArray() {
return buildDefaultComponentsArray();
}
protected Expression createRecord(SortedMap<Integer, Expression> recordComponents) {
RecordComponent[] components = RecordUtils.getRecordComponents(beanClass);
Object[] defaultValues = RecordUtils.buildRecordComponentDefaultValues(beanClass);
for (int i = 0; i < defaultValues.length; i++) {
if (!recordComponents.containsKey(i)) {
Object defaultValue = defaultValues[i];
assert components != null;
RecordComponent component = components[i];
recordComponents.put(i, new Literal(defaultValue, TypeToken.of(component.getType())));
}
}
Expression[] params = recordComponents.values().toArray(new Expression[0]);
return new Expression.NewInstance(beanType, params);
}
@Override
protected Expression setFieldValue(Expression bean, Descriptor descriptor, Expression value) {
if (descriptor.getField() == null) {
// Field doesn't exist in current class, skip set this field value.
// Note that the field value shouldn't be an inlined value, otherwise field value read may
// be ignored.
// Add an ignored call here to make expression type to void.
return new Expression.StaticInvoke(Utils.class, "ignore", value);
}
return super.setFieldValue(bean, descriptor, value);
}
}