diff --git a/java/benchmark/pom.xml b/java/benchmark/pom.xml index e8687183d1..81460f8ec2 100644 --- a/java/benchmark/pom.xml +++ b/java/benchmark/pom.xml @@ -139,16 +139,6 @@ smoothie-map 2.0.2 - - com.google.protobuf - protobuf-java - ${protobuf.version} - - - com.google.flatbuffers - flatbuffers-java - 2.0.3 - org.slf4j slf4j-api 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 5ccdafd42c..d2ac871a63 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 @@ -462,6 +462,29 @@ public void writeRef(MemoryBuffer buffer, T obj, Serializer serializer) { } } + public void writeRefNullable( + MemoryBuffer buffer, T obj, Serializer serializer, boolean nullable) { + if (serializer.needToWriteRef()) { + if (!refResolver.writeRefOrNull(buffer, obj)) { + depth++; + serializer.write(buffer, obj); + depth--; + } + } else { + if (nullable) { + if (obj == null) { + buffer.writeByte(Fury.NULL_FLAG); + return; + } else { + buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); + } + } + depth++; + serializer.write(buffer, obj); + depth--; + } + } + /** Write object class and data without tracking ref. */ public void writeNullable(MemoryBuffer buffer, Object obj) { if (obj == null) { @@ -682,7 +705,7 @@ public String readString(MemoryBuffer buffer) { return stringSerializer.readString(buffer); } - public void writeJavaStringRef(MemoryBuffer buffer, String str) { + public void writeNullableJavaStringRef(MemoryBuffer buffer, String str) { if (stringSerializer.needToWriteRef()) { if (!refResolver.writeRefOrNull(buffer, str)) { stringSerializer.writeJavaString(buffer, str); @@ -697,6 +720,25 @@ public void writeJavaStringRef(MemoryBuffer buffer, String str) { } } + public void writeNullableJavaStringRef(MemoryBuffer buffer, String str, boolean nullable) { + if (stringSerializer.needToWriteRef()) { + if (!refResolver.writeRefOrNull(buffer, str)) { + stringSerializer.writeJavaString(buffer, str); + } + } else { + if (nullable) { + if (str == null) { + buffer.writeByte(Fury.NULL_FLAG); + } else { + buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); + stringSerializer.write(buffer, str); + } + } else { + stringSerializer.write(buffer, str); + } + } + } + public String readJavaStringRef(MemoryBuffer buffer) { RefResolver refResolver = this.refResolver; if (stringSerializer.needToWriteRef()) { @@ -719,6 +761,32 @@ public String readJavaStringRef(MemoryBuffer buffer) { } } + public String readJavaStringRef(MemoryBuffer buffer, boolean nullable) { + RefResolver refResolver = this.refResolver; + if (stringSerializer.needToWriteRef()) { + String obj; + int nextReadRefId = refResolver.tryPreserveRefId(buffer); + if (nextReadRefId >= NOT_NULL_VALUE_FLAG) { + obj = stringSerializer.read(buffer); + refResolver.setReadObject(nextReadRefId, obj); + return obj; + } else { + return (String) refResolver.getReadObject(); + } + } else { + if (nullable) { + byte headFlag = buffer.readByte(); + if (headFlag == Fury.NULL_FLAG) { + return null; + } else { + return stringSerializer.read(buffer); + } + } else { + return stringSerializer.read(buffer); + } + } + } + public void writeJavaString(MemoryBuffer buffer, String str) { stringSerializer.writeJavaString(buffer, str); } @@ -916,6 +984,29 @@ public T readRef(MemoryBuffer buffer, Serializer serializer) { } } + @SuppressWarnings("unchecked") + public T readRefNullable(MemoryBuffer buffer, Serializer serializer, boolean nullable) { + if (serializer.needToWriteRef()) { + T obj; + int nextReadRefId = refResolver.tryPreserveRefId(buffer); + if (nextReadRefId >= NOT_NULL_VALUE_FLAG) { + obj = serializer.read(buffer); + refResolver.setReadObject(nextReadRefId, obj); + return obj; + } else { + return (T) refResolver.getReadObject(); + } + } else { + if (nullable) { + byte headFlag = buffer.readByte(); + if (headFlag == Fury.NULL_FLAG) { + return null; + } + } + return serializer.read(buffer); + } + } + /** Deserialize not-null and non-reference object from buffer. */ public Object readNonRef(MemoryBuffer buffer) { return readDataInternal(buffer, classResolver.readClassInfo(buffer)); @@ -935,15 +1026,6 @@ public Object readNullable(MemoryBuffer buffer) { } } - public Object readNullable(MemoryBuffer buffer, ClassInfoHolder classInfoHolder) { - byte headFlag = buffer.readByte(); - if (headFlag == Fury.NULL_FLAG) { - return null; - } else { - return readNonRef(buffer, classInfoHolder); - } - } - /** Class should be read already. */ public Object readData(MemoryBuffer buffer, ClassInfo classInfo) { depth++; diff --git a/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java b/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java new file mode 100644 index 0000000000..67b943698b --- /dev/null +++ b/java/fury-core/src/main/java/org/apache/fury/annotation/FuryField.java @@ -0,0 +1,33 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FuryField { + + /** Whether field is nullable. */ + boolean nullable() default true; +} diff --git a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java index 17d6b2e80e..3ee6a3408c 100644 --- a/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java +++ b/java/fury-core/src/main/java/org/apache/fury/resolver/FieldResolver.java @@ -43,12 +43,14 @@ import java.util.TreeSet; import java.util.stream.Collectors; import org.apache.fury.Fury; +import org.apache.fury.annotation.FuryField; import org.apache.fury.collection.Tuple2; import org.apache.fury.exception.ClassNotCompatibleException; import org.apache.fury.memory.MemoryBuffer; import org.apache.fury.reflect.FieldAccessor; import org.apache.fury.reflect.ReflectionUtils; import org.apache.fury.reflect.TypeRef; +import org.apache.fury.serializer.AbstractObjectSerializer; import org.apache.fury.serializer.PrimitiveSerializers; import org.apache.fury.serializer.collection.AbstractCollectionSerializer; import org.apache.fury.serializer.collection.AbstractMapSerializer; @@ -748,6 +750,7 @@ public static class FieldInfo { protected final ClassResolver classResolver; private final FieldAccessor fieldAccessor; private final ClassInfoHolder classInfoHolder; + private final AbstractObjectSerializer.FuryFieldAnnotationInfo furyFieldAnnotationInfo; public FieldInfo( Fury fury, @@ -772,6 +775,9 @@ public FieldInfo( } else { fieldAccessor = FieldAccessor.createAccessor(field); } + this.furyFieldAnnotationInfo = + new AbstractObjectSerializer.FuryFieldAnnotationInfo( + field == null ? null : field.getAnnotation(FuryField.class)); } public static FieldInfo of( @@ -838,6 +844,10 @@ public static FieldInfo of( } } + public AbstractObjectSerializer.FuryFieldAnnotationInfo getFuryFieldAnnotationInfo() { + return furyFieldAnnotationInfo; + } + public String getName() { return name; } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java index a6e3af8605..fd74636d3e 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/AbstractObjectSerializer.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.fury.Fury; +import org.apache.fury.annotation.FuryField; import org.apache.fury.collection.Tuple2; import org.apache.fury.collection.Tuple3; import org.apache.fury.memory.Platform; @@ -376,7 +377,10 @@ protected T newBean() { descriptor.getField() != null ? FieldAccessor.createAccessor(descriptor.getField()) : null, - fury); + fury, + descriptor.getField() != null + ? descriptor.getField().getAnnotation(FuryField.class) + : null); otherFields[cnt++] = genericTypeField; } cnt = 0; @@ -398,7 +402,8 @@ private static FinalTypeField buildFinalTypeField(Fury fury, Descriptor d) { d.getDeclaringClass() + "." + d.getName(), // `d.getField()` will be null when peer class doesn't have this field. d.getField() != null ? FieldAccessor.createAccessor(d.getField()) : null, - fury); + fury, + d.getField() != null ? d.getField().getAnnotation(FuryField.class) : null); } private static GenericTypeField buildContainerField(Fury fury, Descriptor d) { @@ -406,19 +411,46 @@ private static GenericTypeField buildContainerField(Fury fury, Descriptor d) { d.getTypeRef(), d.getDeclaringClass() + "." + d.getName(), d.getField() != null ? FieldAccessor.createAccessor(d.getField()) : null, - fury); + fury, + d.getField() != null ? d.getField().getAnnotation(FuryField.class) : null); + } + + /** + * This class is used to store the properties of the annotation {@link FuryField}, avoiding the + * reflection overhead when using annotations to obtain properties. + */ + public static final class FuryFieldAnnotationInfo { + + // default value is true + public boolean nullable = true; + + public FuryFieldAnnotationInfo(FuryField furyField) { + if (furyField != null) { + this.nullable = furyField.nullable(); + } + } + + @Override + public String toString() { + return "FuryFieldAnnotationInfo{" + "nullable=" + nullable + '}'; + } } public static class InternalFieldInfo { protected final short classId; protected final String qualifiedFieldName; protected final FieldAccessor fieldAccessor; + protected FuryFieldAnnotationInfo fieldAnnotationInfo; private InternalFieldInfo( - short classId, String qualifiedFieldName, FieldAccessor fieldAccessor) { + short classId, + String qualifiedFieldName, + FieldAccessor fieldAccessor, + FuryField furyField) { this.classId = classId; this.qualifiedFieldName = qualifiedFieldName; this.fieldAccessor = fieldAccessor; + this.fieldAnnotationInfo = new FuryFieldAnnotationInfo(furyField); } @Override @@ -430,6 +462,8 @@ public String toString() { + qualifiedFieldName + ", field=" + (fieldAccessor != null ? fieldAccessor.getField() : null) + + ", FuryFieldAnnotationInfo=" + + fieldAnnotationInfo + '}'; } } @@ -437,8 +471,9 @@ public String toString() { static final class FinalTypeField extends InternalFieldInfo { final ClassInfo classInfo; - private FinalTypeField(Class type, String fieldName, FieldAccessor accessor, Fury fury) { - super(getRegisteredClassId(fury, type), fieldName, accessor); + private FinalTypeField( + Class type, String fieldName, FieldAccessor accessor, Fury fury, FuryField furyField) { + super(getRegisteredClassId(fury, type), fieldName, accessor, furyField); // invoke `copy` to avoid ObjectSerializer construct clear serializer by `clearSerializer`. if (type == FinalObjectTypeStub.class) { // `FinalObjectTypeStub` has no fields, using its `classInfo` @@ -456,8 +491,12 @@ static final class GenericTypeField extends InternalFieldInfo { final boolean trackingRef; private GenericTypeField( - Class cls, String qualifiedFieldName, FieldAccessor accessor, Fury fury) { - super(getRegisteredClassId(fury, cls), qualifiedFieldName, accessor); + Class cls, + String qualifiedFieldName, + FieldAccessor accessor, + Fury fury, + FuryField furyField) { + super(getRegisteredClassId(fury, cls), qualifiedFieldName, accessor, furyField); // TODO support generics in Pojo, see ComplexObjectSerializer.getGenericTypes genericType = fury.getClassResolver().buildGenericType(cls); classInfoHolder = fury.getClassResolver().nilClassInfoHolder(); @@ -465,8 +504,13 @@ private GenericTypeField( } private GenericTypeField( - TypeRef typeRef, String qualifiedFieldName, FieldAccessor accessor, Fury fury) { - super(getRegisteredClassId(fury, getRawType(typeRef)), qualifiedFieldName, accessor); + TypeRef typeRef, + String qualifiedFieldName, + FieldAccessor accessor, + Fury fury, + FuryField furyField) { + super( + getRegisteredClassId(fury, getRawType(typeRef)), qualifiedFieldName, accessor, furyField); // TODO support generics in Pojo, see ComplexObjectSerializer.getGenericTypes genericType = fury.getClassResolver().buildGenericType(typeRef); classInfoHolder = fury.getClassResolver().nilClassInfoHolder(); @@ -484,6 +528,8 @@ public String toString() { + qualifiedFieldName + ", field=" + (fieldAccessor != null ? fieldAccessor.getField() : null) + + ", FuryFieldAnnotationInfo=" + + fieldAnnotationInfo + '}'; } } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/CompatibleSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/CompatibleSerializer.java index 433093d88a..c66d2d07cc 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/CompatibleSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/CompatibleSerializer.java @@ -134,13 +134,15 @@ private void readAndWriteFieldValue( fury, buffer, targetObject, fieldAccessor, classId)) { Object fieldValue; fieldValue = fieldAccessor.getObject(targetObject); - if (ObjectSerializer.writeBasicObjectFieldValueFailed(fury, buffer, fieldValue, classId)) { + if (ObjectSerializer.writeBasicObjectFieldValueFailed( + fury, buffer, fieldValue, classId, fieldInfo.getFuryFieldAnnotationInfo())) { if (classId == ClassResolver.NO_CLASS_ID) { // SEPARATE_TYPES_HASH writeSeparateFieldValue(fieldInfo, buffer, fieldValue); } else { ClassInfo classInfo = fieldInfo.getClassInfo(classId); Serializer serializer = classInfo.getSerializer(); - fury.writeRef(buffer, fieldValue, serializer); + fury.writeRefNullable( + buffer, fieldValue, serializer, fieldInfo.getFuryFieldAnnotationInfo().nullable); } } } @@ -180,7 +182,8 @@ private void writeFieldValue( buffer.writeFloat64((Double) fieldValue); return; case ClassResolver.STRING_CLASS_ID: - fury.writeJavaStringRef(buffer, (String) fieldValue); + fury.writeNullableJavaStringRef( + buffer, (String) fieldValue, fieldInfo.getFuryFieldAnnotationInfo().nullable); break; case ClassResolver.NO_CLASS_ID: // SEPARATE_TYPES_HASH writeSeparateFieldValue(fieldInfo, buffer, fieldValue); @@ -189,7 +192,8 @@ private void writeFieldValue( { ClassInfo classInfo = fieldInfo.getClassInfo(classId); Serializer serializer = classInfo.getSerializer(); - fury.writeRef(buffer, fieldValue, serializer); + fury.writeRefNullable( + buffer, fieldValue, serializer, fieldInfo.getFuryFieldAnnotationInfo().nullable); } } } @@ -559,9 +563,19 @@ private void readAndSetFieldValue( FieldAccessor fieldAccessor = fieldInfo.getFieldAccessor(); short classId = fieldInfo.getEmbeddedClassId(); if (ObjectSerializer.readPrimitiveFieldValueFailed( - fury, buffer, targetObject, fieldAccessor, classId) + fury, + buffer, + targetObject, + fieldAccessor, + classId, + fieldInfo.getFuryFieldAnnotationInfo()) && ObjectSerializer.readBasicObjectFieldValueFailed( - fury, buffer, targetObject, fieldAccessor, classId)) { + fury, + buffer, + targetObject, + fieldAccessor, + classId, + fieldInfo.getFuryFieldAnnotationInfo())) { if (classId == ClassResolver.NO_CLASS_ID) { // SEPARATE_TYPES_HASH Object fieldValue = fieldResolver.readObjectField(buffer, fieldInfo); @@ -569,7 +583,10 @@ private void readAndSetFieldValue( } else { ClassInfo classInfo = fieldInfo.getClassInfo(classId); Serializer serializer = classInfo.getSerializer(); - fieldAccessor.putObject(targetObject, fury.readRef(buffer, serializer)); + fieldAccessor.putObject( + targetObject, + fury.readRefNullable( + buffer, serializer, fieldInfo.getFuryFieldAnnotationInfo().nullable)); } } } @@ -599,14 +616,15 @@ private Object readFieldValue(FieldResolver.FieldInfo fieldInfo, MemoryBuffer bu case ClassResolver.PRIMITIVE_DOUBLE_CLASS_ID: return buffer.readFloat64(); case ClassResolver.STRING_CLASS_ID: - return fury.readJavaStringRef(buffer); + return fury.readJavaStringRef(buffer, fieldInfo.getFuryFieldAnnotationInfo().nullable); case ClassResolver.NO_CLASS_ID: return fieldResolver.readObjectField(buffer, fieldInfo); default: { ClassInfo classInfo = fieldInfo.getClassInfo(classId); Serializer serializer = classInfo.getSerializer(); - return fury.readRef(buffer, serializer); + return fury.readRefNullable( + buffer, serializer, fieldInfo.getFuryFieldAnnotationInfo().nullable); } } } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java index ee9628728a..4e07e15b6a 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/MetaSharedSerializer.java @@ -151,9 +151,9 @@ public T read(MemoryBuffer buffer) { if (fieldAccessor != null) { short classId = fieldInfo.classId; if (ObjectSerializer.readPrimitiveFieldValueFailed( - fury, buffer, obj, fieldAccessor, classId) + fury, buffer, obj, fieldAccessor, classId, fieldInfo.fieldAnnotationInfo) && ObjectSerializer.readBasicObjectFieldValueFailed( - fury, buffer, obj, fieldAccessor, classId)) { + fury, buffer, obj, fieldAccessor, classId, fieldInfo.fieldAnnotationInfo)) { assert fieldInfo.classInfo != null; Object fieldValue = ObjectSerializer.readFinalObjectFieldValue( diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java index c6faf3e773..76c513839b 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/ObjectSerializer.java @@ -134,16 +134,28 @@ public void write(MemoryBuffer buffer, T value) { } // write order: primitive,boxed,final,other,collection,map writeFinalFields(buffer, value, fury, refResolver, classResolver); + writeOtherFields(buffer, value, fury, classResolver); + writeContainerFields(buffer, value, fury, refResolver, classResolver); + } + + private void writeOtherFields( + MemoryBuffer buffer, T value, Fury fury, ClassResolver classResolver) { for (GenericTypeField fieldInfo : otherFields) { FieldAccessor fieldAccessor = fieldInfo.fieldAccessor; Object fieldValue = fieldAccessor.getObject(value); if (fieldInfo.trackingRef) { fury.writeRef(buffer, fieldValue, fieldInfo.classInfoHolder); } else { - fury.writeNullable(buffer, fieldValue, fieldInfo.classInfoHolder); + if (fieldInfo.fieldAnnotationInfo.nullable) { + fury.writeNullable(buffer, fieldValue, fieldInfo.classInfoHolder); + } else { + fury.writeNonRef( + buffer, + fieldValue, + classResolver.getClassInfo(fieldValue.getClass(), fieldInfo.classInfoHolder)); + } } } - writeContainerFields(buffer, value, fury, refResolver, classResolver); } private void writeFinalFields( @@ -160,7 +172,8 @@ private void writeFinalFields( short classId = fieldInfo.classId; if (writePrimitiveFieldValueFailed(fury, buffer, value, fieldAccessor, classId)) { Object fieldValue = fieldAccessor.getObject(value); - if (writeBasicObjectFieldValueFailed(fury, buffer, fieldValue, classId)) { + if (writeBasicObjectFieldValueFailed( + fury, buffer, fieldValue, classId, fieldInfo.fieldAnnotationInfo)) { Serializer serializer = fieldInfo.classInfo.getSerializer(); if (!metaShareEnabled || isFinal[i]) { // whether tracking ref is recorded in `fieldInfo.serializer`, so it's still @@ -174,7 +187,11 @@ private void writeFinalFields( serializer.write(buffer, fieldValue); } } else { - fury.writeNullable(buffer, fieldValue, fieldInfo.classInfo); + if (fieldInfo.fieldAnnotationInfo.nullable) { + fury.writeNullable(buffer, fieldValue, fieldInfo.classInfo); + } else { + fury.writeNonRef(buffer, fieldValue, fieldInfo.classInfo); + } } } } @@ -214,10 +231,10 @@ static void writeContainerFieldValue( generics.popGenericType(); } } else { - if (fieldValue == null) { - buffer.writeByte(Fury.NULL_FLAG); + if (fieldInfo.fieldAnnotationInfo.nullable) { + writeContainerFieldValueNullable( + buffer, fury, fieldInfo, generics, classResolver, fieldValue); } else { - buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); generics.pushGenericType(fieldInfo.genericType); fury.writeNonRef( buffer, @@ -228,6 +245,26 @@ static void writeContainerFieldValue( } } + private static void writeContainerFieldValueNullable( + MemoryBuffer buffer, + Fury fury, + GenericTypeField fieldInfo, + Generics generics, + ClassResolver classResolver, + Object fieldValue) { + if (fieldValue == null) { + buffer.writeByte(Fury.NULL_FLAG); + } else { + buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); + generics.pushGenericType(fieldInfo.genericType); + fury.writeNonRef( + buffer, + fieldValue, + classResolver.getClassInfo(fieldValue.getClass(), fieldInfo.classInfoHolder)); + generics.popGenericType(); + } + } + @Override public T read(MemoryBuffer buffer) { if (isRecord) { @@ -301,8 +338,10 @@ public T readAndSetFields(MemoryBuffer buffer, T obj) { boolean isFinal = !metaShareEnabled || this.isFinal[i]; FieldAccessor fieldAccessor = fieldInfo.fieldAccessor; short classId = fieldInfo.classId; - if (readPrimitiveFieldValueFailed(fury, buffer, obj, fieldAccessor, classId) - && readBasicObjectFieldValueFailed(fury, buffer, obj, fieldAccessor, classId)) { + if (readPrimitiveFieldValueFailed( + fury, buffer, obj, fieldAccessor, classId, fieldInfo.fieldAnnotationInfo) + && readBasicObjectFieldValueFailed( + fury, buffer, obj, fieldAccessor, classId, fieldInfo.fieldAnnotationInfo)) { Object fieldValue = readFinalObjectFieldValue(fury, refResolver, classResolver, fieldInfo, isFinal, buffer); fieldAccessor.putObject(obj, fieldValue); @@ -350,11 +389,15 @@ static Object readFinalObjectFieldValue( fieldValue = refResolver.getReadObject(); } } else { - byte headFlag = buffer.readByte(); - if (headFlag == Fury.NULL_FLAG) { - fieldValue = null; + if (fieldInfo.fieldAnnotationInfo.nullable) { + byte headFlag = buffer.readByte(); + if (headFlag == Fury.NULL_FLAG) { + fieldValue = null; + } else { + classResolver.readClassInfo(buffer, fieldInfo.classInfo); + fieldValue = serializer.read(buffer); + } } else { - classResolver.readClassInfo(buffer, fieldInfo.classInfo); fieldValue = serializer.read(buffer); } } @@ -367,9 +410,13 @@ static Object readOtherFieldValue(Fury fury, GenericTypeField fieldInfo, MemoryB if (fieldInfo.trackingRef) { fieldValue = fury.readRef(buffer, fieldInfo.classInfoHolder); } else { - byte headFlag = buffer.readByte(); - if (headFlag == Fury.NULL_FLAG) { - fieldValue = null; + if (fieldInfo.fieldAnnotationInfo.nullable) { + byte headFlag = buffer.readByte(); + if (headFlag == Fury.NULL_FLAG) { + fieldValue = null; + } else { + fieldValue = fury.readNonRef(buffer, fieldInfo.classInfoHolder); + } } else { fieldValue = fury.readNonRef(buffer, fieldInfo.classInfoHolder); } @@ -385,9 +432,15 @@ static Object readContainerFieldValue( fieldValue = fury.readRef(buffer, fieldInfo.classInfoHolder); generics.popGenericType(); } else { - byte headFlag = buffer.readByte(); - if (headFlag == Fury.NULL_FLAG) { - fieldValue = null; + if (fieldInfo.fieldAnnotationInfo.nullable) { + byte headFlag = buffer.readByte(); + if (headFlag == Fury.NULL_FLAG) { + fieldValue = null; + } else { + generics.pushGenericType(fieldInfo.genericType); + fieldValue = fury.readNonRef(buffer, fieldInfo.classInfoHolder); + generics.popGenericType(); + } } else { generics.pushGenericType(fieldInfo.genericType); fieldValue = fury.readNonRef(buffer, fieldInfo.classInfoHolder); @@ -495,102 +548,160 @@ static boolean writePrimitiveFieldValueFailed( * @return true if field value isn't written by this function. */ static boolean writeBasicObjectFieldValueFailed( - Fury fury, MemoryBuffer buffer, Object fieldValue, short classId) { + Fury fury, + MemoryBuffer buffer, + Object fieldValue, + short classId, + FuryFieldAnnotationInfo fieldAnnotationInfo) { if (!fury.isBasicTypesRefIgnored()) { return true; // let common path handle this. } + boolean nullable = fieldAnnotationInfo.nullable; // add time types serialization here. switch (classId) { - case ClassResolver.STRING_CLASS_ID: // fastpath for string. - fury.writeJavaStringRef(buffer, (String) (fieldValue)); + case ClassResolver.STRING_CLASS_ID: // fast path for string. + fury.writeNullableJavaStringRef(buffer, (String) (fieldValue), nullable); return false; case ClassResolver.BOOLEAN_CLASS_ID: - { - if (fieldValue == null) { - buffer.writeByte(Fury.NULL_FLAG); - } else { - buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeBoolean((Boolean) (fieldValue)); - } - return false; - } + writeNullableBoolean(buffer, fieldValue, nullable); + return false; case ClassResolver.BYTE_CLASS_ID: - { + writeNullableByte(buffer, fieldValue, nullable); + return false; + case ClassResolver.CHAR_CLASS_ID: + writeNullableChar(buffer, fieldValue, nullable); + return false; + case ClassResolver.SHORT_CLASS_ID: + writeNullableShort(buffer, fieldValue, nullable); + return false; + case ClassResolver.INTEGER_CLASS_ID: + writeNullableInteger(fury, buffer, fieldValue, nullable); + return false; + case ClassResolver.FLOAT_CLASS_ID: + writeNullableFloat(buffer, fieldValue, nullable); + return false; + case ClassResolver.LONG_CLASS_ID: + writeNullableLong(fury, buffer, fieldValue, nullable); + return false; + case ClassResolver.DOUBLE_CLASS_ID: + writeNullableDouble(buffer, fieldValue, nullable); + return false; + default: + return true; + } + } + + + private static void writeNullableDouble(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeByte((Byte) (fieldValue)); + buffer.writeFloat64((Double) fieldValue); } - return false; + } else { + buffer.writeFloat64((Double) fieldValue); } - case ClassResolver.CHAR_CLASS_ID: - { + } + + private static void writeNullableLong(Fury fury, MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeChar((Character) (fieldValue)); + fury.writeInt64(buffer, (Long) fieldValue); } - return false; + } else { + fury.writeInt64(buffer, (Long) fieldValue); } - case ClassResolver.SHORT_CLASS_ID: - { + } + + private static void writeNullableFloat(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeInt16((Short) (fieldValue)); + buffer.writeFloat32((Float) fieldValue); } - return false; + } else { + buffer.writeFloat32((Float) fieldValue); } - case ClassResolver.INTEGER_CLASS_ID: - { + } + + private static void writeNullableInteger(Fury fury, MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); if (fury.compressInt()) { - buffer.writeVarInt32((Integer) (fieldValue)); + buffer.writeVarInt32((Integer) fieldValue); } else { - buffer.writeInt32((Integer) (fieldValue)); + buffer.writeInt32((Integer) fieldValue); } } - return false; + } else { + if (fury.compressInt()) { + buffer.writeVarInt32((Integer) fieldValue); + } else { + buffer.writeInt32((Integer) fieldValue); + } } - case ClassResolver.FLOAT_CLASS_ID: - { + } + + private static void writeNullableShort(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeFloat32((Float) (fieldValue)); + buffer.writeInt16((Short) fieldValue); } - return false; + } else { + buffer.writeInt16((Short) fieldValue); } - case ClassResolver.LONG_CLASS_ID: - { + } + + private static void writeNullableChar(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - fury.writeInt64(buffer, (Long) fieldValue); + buffer.writeChar((Character) fieldValue); } - return false; + } else { + buffer.writeChar((Character) fieldValue); } - case ClassResolver.DOUBLE_CLASS_ID: - { + } + + private static void writeNullableByte(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { if (fieldValue == null) { buffer.writeByte(Fury.NULL_FLAG); } else { buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - buffer.writeFloat64((Double) (fieldValue)); + buffer.writeByte((Byte) fieldValue); } - return false; + } else { + buffer.writeByte((Byte) fieldValue); } - default: - return true; } + + private static void writeNullableBoolean(MemoryBuffer buffer, Object fieldValue, boolean nullable) { + if (nullable) { + if (fieldValue == null) { + buffer.writeByte(Fury.NULL_FLAG); + } else { + buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); + buffer.writeBoolean((Boolean) (fieldValue)); + } + } else { + buffer.writeBoolean((Boolean) (fieldValue)); + } } /** @@ -604,10 +715,12 @@ static boolean readPrimitiveFieldValueFailed( MemoryBuffer buffer, Object targetObject, FieldAccessor fieldAccessor, - short classId) { + short classId, + FuryFieldAnnotationInfo furyFieldAnnotationInfo) { long fieldOffset = fieldAccessor.getFieldOffset(); if (fieldOffset != -1) { - return readPrimitiveFieldValueFailed(fury, buffer, targetObject, fieldOffset, classId); + return readPrimitiveFieldValueFailed( + fury, buffer, targetObject, fieldOffset, classId, furyFieldAnnotationInfo); } switch (classId) { case ClassResolver.PRIMITIVE_BOOLEAN_CLASS_ID: @@ -639,7 +752,8 @@ static boolean readPrimitiveFieldValueFailed( fieldAccessor.set(targetObject, buffer.readFloat64()); return false; case ClassResolver.STRING_CLASS_ID: - fieldAccessor.putObject(targetObject, fury.readJavaStringRef(buffer)); + fieldAccessor.putObject( + targetObject, fury.readJavaStringRef(buffer, furyFieldAnnotationInfo.nullable)); return false; default: { @@ -649,7 +763,12 @@ static boolean readPrimitiveFieldValueFailed( } private static boolean readPrimitiveFieldValueFailed( - Fury fury, MemoryBuffer buffer, Object targetObject, long fieldOffset, short classId) { + Fury fury, + MemoryBuffer buffer, + Object targetObject, + long fieldOffset, + short classId, + FuryFieldAnnotationInfo furyFieldAnnotationInfo) { switch (classId) { case ClassResolver.PRIMITIVE_BOOLEAN_CLASS_ID: Platform.putBoolean(targetObject, fieldOffset, buffer.readBoolean()); @@ -680,7 +799,10 @@ private static boolean readPrimitiveFieldValueFailed( Platform.putDouble(targetObject, fieldOffset, buffer.readFloat64()); return false; case ClassResolver.STRING_CLASS_ID: - Platform.putObject(targetObject, fieldOffset, fury.readJavaStringRef(buffer)); + Platform.putObject( + targetObject, + fieldOffset, + fury.readJavaStringRef(buffer, furyFieldAnnotationInfo.nullable)); return false; default: { @@ -694,53 +816,63 @@ static boolean readBasicObjectFieldValueFailed( MemoryBuffer buffer, Object targetObject, FieldAccessor fieldAccessor, - short classId) { + short classId, + FuryFieldAnnotationInfo furyFieldAnnotationInfo) { if (!fury.isBasicTypesRefIgnored()) { return true; // let common path handle this. } + boolean nullable = furyFieldAnnotationInfo.nullable; // add time types serialization here. switch (classId) { - case ClassResolver.STRING_CLASS_ID: // fastpath for string. - fieldAccessor.putObject(targetObject, fury.readJavaStringRef(buffer)); + case ClassResolver.STRING_CLASS_ID: // fast path for string. + fieldAccessor.putObject(targetObject, fury.readJavaStringRef(buffer, nullable)); return false; case ClassResolver.BOOLEAN_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readBoolean()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readBoolean()); } + return false; case ClassResolver.BYTE_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readByte()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readByte()); } + return false; case ClassResolver.CHAR_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readChar()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readChar()); } + return false; case ClassResolver.SHORT_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readInt16()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readInt16()); } + return false; case ClassResolver.INTEGER_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { @@ -750,35 +882,47 @@ static boolean readBasicObjectFieldValueFailed( fieldAccessor.putObject(targetObject, buffer.readInt32()); } } - return false; + } else { + if (fury.compressInt()) { + fieldAccessor.putObject(targetObject, buffer.readVarInt32()); + } else { + fieldAccessor.putObject(targetObject, buffer.readInt32()); + } } + return false; case ClassResolver.FLOAT_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readFloat32()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readFloat32()); } + return false; case ClassResolver.LONG_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, fury.readInt64(buffer)); } - return false; + } else { + fieldAccessor.putObject(targetObject, fury.readInt64(buffer)); } + return false; case ClassResolver.DOUBLE_CLASS_ID: - { + if (nullable) { if (buffer.readByte() == Fury.NULL_FLAG) { fieldAccessor.putObject(targetObject, null); } else { fieldAccessor.putObject(targetObject, buffer.readFloat64()); } - return false; + } else { + fieldAccessor.putObject(targetObject, buffer.readFloat64()); } + return false; default: return true; } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializers.java b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializers.java index 2ca878705f..424869b45e 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializers.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializers.java @@ -344,7 +344,7 @@ public StringKeyMapSerializer(Fury fury, Class> cls) { public void write(MemoryBuffer buffer, Map value) { buffer.writeVarUint32Small7(value.size()); for (Map.Entry e : value.entrySet()) { - fury.writeJavaStringRef(buffer, e.getKey()); + fury.writeNullableJavaStringRef(buffer, e.getKey()); // If value is a collection, the `newCollection` method will record itself to // reference map, which may get wrong index if this value is written without index. fury.writeRef(buffer, e.getValue()); diff --git a/java/fury-core/src/test/java/org/apache/fury/FuryTest.java b/java/fury-core/src/test/java/org/apache/fury/FuryTest.java index 127529961b..52878174c7 100644 --- a/java/fury-core/src/test/java/org/apache/fury/FuryTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/FuryTest.java @@ -49,10 +49,13 @@ import java.util.TreeSet; import java.util.UUID; import java.util.WeakHashMap; + +import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import org.apache.fury.annotation.Expose; +import org.apache.fury.annotation.FuryField; import org.apache.fury.annotation.Ignore; import org.apache.fury.builder.Generated; import org.apache.fury.config.CompatibleMode; @@ -434,6 +437,63 @@ private static class IgnoreFields { long f3; } + @Data + private static class FieldsInfoAnnotationClass { + @FuryField(nullable = false) + Integer i1 = Integer.valueOf(1); + + @FuryField(nullable = false) + String s = "str"; + + @FuryField(nullable = false) + Short shortValue = Short.valueOf((short) 2); + + @FuryField(nullable = false) + Byte byteValue = Byte.valueOf((byte) 3); + + @FuryField(nullable = false) + Long longValue = Long.valueOf(4L); + + @FuryField(nullable = false) + Boolean booleanValue = Boolean.TRUE; + + @FuryField(nullable = false) + Float floatValue = Float.valueOf(5.0f); + + @FuryField(nullable = false) + Double doubleValue = Double.valueOf(6.0); + + @FuryField(nullable = false) + Character character = Character.valueOf('c'); + + int i2; + + long l1; + + float f1; + + double d1; + + char c1; + + boolean b1; + + byte byte1; + + @FuryField(nullable = false) + List integerList = Lists.newArrayList(1); + } + + @Test + public void testFuryFieldsAnnotation() { + Fury fury = Fury.builder().requireClassRegistration(false).withCodegen(false).build(); + final FieldsInfoAnnotationClass s = new FieldsInfoAnnotationClass(); + FieldsInfoAnnotationClass o = serDe(fury, s); + assertEquals(o, s); + s.setIntegerList(null); + assertThrows(NullPointerException.class, () -> serDe(fury, s)); + } + @Test public void testIgnoreFields() { Fury fury = Fury.builder().requireClassRegistration(false).build();