Skip to content

Commit 0922ec7

Browse files
fix(java): row format generated bean types handling Optional (#2497)
Incorrect check leads to losing OptionalInt, OptionalLong, and OptionalDouble values with generated bean implementation
1 parent 1735e4d commit 0922ec7

3 files changed

Lines changed: 81 additions & 12 deletions

File tree

java/fory-core/src/main/java/org/apache/fory/type/TypeUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,17 @@ public static boolean isBean(TypeRef<?> typeRef, TypeResolutionContext ctx) {
656656
}
657657
}
658658

659+
/**
660+
* Check if a class is one of {@link Optional), {@link OptionalInt},
661+
* {@link OptionaLong}, or {@link OptionalDouble}.
662+
*/
663+
public static boolean isOptionalType(Class<?> type) {
664+
return type == Optional.class
665+
|| type == OptionalInt.class
666+
|| type == OptionalLong.class
667+
|| type == OptionalDouble.class;
668+
}
669+
659670
private static boolean isSynthesizableInterface(Class<?> cls) {
660671
return cls.isInterface()
661672
&& !Collection.class.isAssignableFrom(cls)

java/fory-format/src/main/java/org/apache/fory/format/encoder/RowEncoderBuilder.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,7 @@ public Expression buildDecodeExpression() {
285285

286286
private static Expression nullValue(TypeRef<?> fieldType) {
287287
Class<?> rawType = fieldType.getRawType();
288-
if (rawType == Optional.class
289-
|| rawType == OptionalInt.class
290-
|| rawType == OptionalLong.class
291-
|| rawType == OptionalDouble.class) {
288+
if (TypeUtils.isOptionalType(rawType)) {
292289
return new Expression.StaticInvoke(rawType, "empty", "", fieldType, false, true);
293290
}
294291
return new Expression.Reference(TypeUtils.defaultValue(rawType), fieldType);
@@ -361,7 +358,7 @@ private CodegenContext buildImplClass() {
361358
Expression storeValue =
362359
new Expression.SetField(new Expression.Reference("this"), fieldName, decodeValue);
363360
Expression shouldLoad;
364-
if (rawFieldType == Optional.class) {
361+
if (TypeUtils.isOptionalType(rawFieldType)) {
365362
shouldLoad =
366363
new Expression.Not(
367364
Expression.Invoke.inlineInvoke(fieldRef, "isPresent", TypeUtils.BOOLEAN_TYPE));

java/fory-format/src/test/java/org/apache/fory/format/encoder/ImplementInterfaceTest.java

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
import java.util.Arrays;
2323
import java.util.List;
2424
import java.util.Optional;
25+
import java.util.OptionalDouble;
26+
import java.util.OptionalInt;
27+
import java.util.OptionalLong;
2528
import java.util.TreeSet;
29+
2630
import lombok.Data;
2731
import org.apache.arrow.vector.types.pojo.Field;
2832
import org.apache.fory.annotation.ForyField;
@@ -141,35 +145,56 @@ public PoisonPill decode(final byte[] value) {
141145

142146
public interface OptionalType {
143147
Optional<String> f1();
148+
OptionalInt f2();
149+
OptionalLong f3();
150+
OptionalDouble f4();
144151
}
145152

146153
static class OptionalTypeImpl implements OptionalType {
147-
private final Optional<String> f1;
148-
149-
OptionalTypeImpl(final Optional<String> f1) {
150-
this.f1 = f1;
151-
}
154+
Optional<String> f1;
155+
OptionalInt f2;
156+
OptionalLong f3;
157+
OptionalDouble f4;
152158

153159
@Override
154160
public Optional<String> f1() {
155161
return f1;
156162
}
163+
164+
@Override
165+
public OptionalInt f2() {
166+
return f2;
167+
}
168+
169+
@Override
170+
public OptionalLong f3() {
171+
return f3;
172+
}
173+
174+
@Override
175+
public OptionalDouble f4() {
176+
return f4;
177+
}
157178
}
158179

159180
@Test
160181
public void testNullOptional() {
161-
final OptionalType bean1 = new OptionalTypeImpl(null);
182+
final OptionalType bean1 = new OptionalTypeImpl();
162183
final RowEncoder<OptionalType> encoder = Encoders.bean(OptionalType.class);
163184
final BinaryRow row = encoder.toRow(bean1);
164185
final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
165186
row.pointTo(buffer, 0, buffer.size());
166187
final OptionalType deserializedBean = encoder.fromRow(row);
167188
Assert.assertEquals(deserializedBean.f1(), Optional.empty());
189+
Assert.assertEquals(deserializedBean.f2(), OptionalInt.empty());
190+
Assert.assertEquals(deserializedBean.f3(), OptionalLong.empty());
191+
Assert.assertEquals(deserializedBean.f4(), OptionalDouble.empty());
168192
}
169193

170194
@Test
171195
public void testPresentOptional() {
172-
final OptionalType bean1 = new OptionalTypeImpl(Optional.of("42"));
196+
final OptionalTypeImpl bean1 = new OptionalTypeImpl();
197+
bean1.f1 = Optional.of("42");
173198
final RowEncoder<OptionalType> encoder = Encoders.bean(OptionalType.class);
174199
final BinaryRow row = encoder.toRow(bean1);
175200
final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
@@ -178,6 +203,42 @@ public void testPresentOptional() {
178203
Assert.assertEquals(deserializedBean.f1(), Optional.of("42"));
179204
}
180205

206+
@Test
207+
public void testPresentOptionalInteger() {
208+
final OptionalTypeImpl bean1 = new OptionalTypeImpl();
209+
bean1.f2 = OptionalInt.of(42);
210+
final RowEncoder<OptionalType> encoder = Encoders.bean(OptionalType.class);
211+
final BinaryRow row = encoder.toRow(bean1);
212+
final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
213+
row.pointTo(buffer, 0, buffer.size());
214+
final OptionalType deserializedBean = encoder.fromRow(row);
215+
Assert.assertEquals(deserializedBean.f2(), OptionalInt.of(42));
216+
}
217+
218+
@Test
219+
public void testPresentOptionalLong() {
220+
final OptionalTypeImpl bean1 = new OptionalTypeImpl();
221+
bean1.f3 = OptionalLong.of(42);
222+
final RowEncoder<OptionalType> encoder = Encoders.bean(OptionalType.class);
223+
final BinaryRow row = encoder.toRow(bean1);
224+
final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
225+
row.pointTo(buffer, 0, buffer.size());
226+
final OptionalType deserializedBean = encoder.fromRow(row);
227+
Assert.assertEquals(deserializedBean.f3(), OptionalLong.of(42));
228+
}
229+
230+
@Test
231+
public void testPresentOptionalDouble() {
232+
final OptionalTypeImpl bean1 = new OptionalTypeImpl();
233+
bean1.f4 = OptionalDouble.of(42.42);
234+
final RowEncoder<OptionalType> encoder = Encoders.bean(OptionalType.class);
235+
final BinaryRow row = encoder.toRow(bean1);
236+
final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
237+
row.pointTo(buffer, 0, buffer.size());
238+
final OptionalType deserializedBean = encoder.fromRow(row);
239+
Assert.assertEquals(deserializedBean.f4(), OptionalDouble.of(42.42));
240+
}
241+
181242
public static class Id<T> {
182243
byte id;
183244

0 commit comments

Comments
 (0)