From 459d5b58cb5a86a740bd553b4ad420edeafcb94f Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Mon, 7 Sep 2020 14:20:27 +0300 Subject: [PATCH] Java Record serialization --- src/one/nio/gen/BytecodeGenerator.java | 3 +- src/one/nio/serial/FieldDescriptor.java | 24 +-- src/one/nio/serial/GeneratedSerializer.java | 20 +- src/one/nio/serial/JsonReader.java | 2 +- src/one/nio/serial/gen/DelegateGenerator.java | 188 ++++++++++-------- src/one/nio/serial/gen/FieldType.java | 2 +- .../util/{SpinWait.java => JavaFeatures.java} | 43 ++-- src/one/nio/util/JavaInternals.java | 38 ++++ test/one/nio/serial/ConversionTest.java | 4 +- test/one/nio/serial/DefaultFieldsTest.java | 6 +- test/one/nio/serial/SerializationTest.java | 124 +++++++++++- 11 files changed, 311 insertions(+), 143 deletions(-) rename src/one/nio/util/{SpinWait.java => JavaFeatures.java} (54%) diff --git a/src/one/nio/gen/BytecodeGenerator.java b/src/one/nio/gen/BytecodeGenerator.java index 6a3047f..2e27e1d 100755 --- a/src/one/nio/gen/BytecodeGenerator.java +++ b/src/one/nio/gen/BytecodeGenerator.java @@ -125,9 +125,8 @@ public static void emitInvoke(MethodVisitor mv, Method m) { public static void emitInvoke(MethodVisitor mv, Constructor c) { String holder = Type.getInternalName(c.getDeclaringClass()); - String name = c.getName(); String sig = Type.getConstructorDescriptor(c); - mv.visitMethodInsn(INVOKESPECIAL, holder, name, sig); + mv.visitMethodInsn(INVOKESPECIAL, holder, "", sig); } public static void emitThrow(MethodVisitor mv, String exceptionClass, String message) { diff --git a/src/one/nio/serial/FieldDescriptor.java b/src/one/nio/serial/FieldDescriptor.java index 678cc9c..8db903c 100755 --- a/src/one/nio/serial/FieldDescriptor.java +++ b/src/one/nio/serial/FieldDescriptor.java @@ -26,8 +26,7 @@ public class FieldDescriptor { private TypeDescriptor typeDescriptor; private Field ownField; private Field parentField; - private boolean useGetter; - private boolean useSetter; + private int index; // Ad-hoc linked list public FieldDescriptor next; @@ -37,11 +36,11 @@ public class FieldDescriptor { this.typeDescriptor = typeDescriptor; } - FieldDescriptor(Field ownField, Field parentField) { + FieldDescriptor(Field ownField, Field parentField, int index) { Renamed renamed = ownField.getAnnotation(Renamed.class); this.nameDescriptor = renamed == null ? ownField.getName() : ownField.getName() + '|' + renamed.from(); this.typeDescriptor = new TypeDescriptor(ownField.getType()); - assignField(ownField, parentField); + assignField(ownField, parentField, index); } @Override @@ -65,27 +64,18 @@ public Field parentField() { return parentField; } - public boolean useGetter() { - return useGetter; - } - - public boolean useSetter() { - return useSetter; + public int index() { + return index; } public boolean is(String nameDescriptor, String typeDescriptor) { return nameDescriptor.equals(this.nameDescriptor) && typeDescriptor.equals(this.typeDescriptor.toString()); } - public void assignField(Field ownField, Field parentField) { + public void assignField(Field ownField, Field parentField, int index) { this.ownField = ownField; this.parentField = parentField; - - SerializeWith serializeWith = ownField.getAnnotation(SerializeWith.class); - if (serializeWith != null) { - this.useGetter = !serializeWith.getter().isEmpty(); - this.useSetter = !serializeWith.setter().isEmpty(); - } + this.index = index; } public static FieldDescriptor read(ObjectInput in) throws IOException { diff --git a/src/one/nio/serial/GeneratedSerializer.java b/src/one/nio/serial/GeneratedSerializer.java index 672ee52..5c0fa4b 100755 --- a/src/one/nio/serial/GeneratedSerializer.java +++ b/src/one/nio/serial/GeneratedSerializer.java @@ -23,7 +23,6 @@ import java.io.Externalizable; import java.io.IOException; -import java.io.NotSerializableException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.reflect.Field; @@ -41,7 +40,7 @@ public class GeneratedSerializer extends Serializer { static final AtomicInteger unsupportedFields = new AtomicInteger(); private FieldDescriptor[] fds; - private ArrayList defaultFields; + private FieldDescriptor[] defaultFields; private Delegate delegate; GeneratedSerializer(Class cls) { @@ -50,8 +49,9 @@ public class GeneratedSerializer extends Serializer { Field[] ownFields = getSerializableFields(); this.fds = new FieldDescriptor[ownFields.length / 2]; for (int i = 0; i < ownFields.length; i += 2) { - fds[i / 2] = new FieldDescriptor(ownFields[i], ownFields[i + 1]); + fds[i / 2] = new FieldDescriptor(ownFields[i], ownFields[i + 1], i / 2); } + this.defaultFields = new FieldDescriptor[0]; checkFieldTypes(); this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class); @@ -89,7 +89,7 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept Field[] ownFields = getSerializableFields(); assignFields(ownFields, true); assignFields(ownFields, false); - assignDefaultFields(ownFields); + this.defaultFields = assignDefaultFields(ownFields); checkFieldTypes(); this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class); @@ -181,26 +181,26 @@ private void assignFields(Field[] ownFields, boolean exactType) { if (fd.ownField() == null) { int found = findField(fd, ownFields, exactType); if (found >= 0) { - fd.assignField(ownFields[found], ownFields[found + 1]); + fd.assignField(ownFields[found], ownFields[found + 1], found / 2); ownFields[found] = null; } } } } - private void assignDefaultFields(Field[] ownFields) { - defaultFields = new ArrayList<>(); + private FieldDescriptor[] assignDefaultFields(Field[] ownFields) { + ArrayList defaultFields = new ArrayList<>(); for (int i = 0; i < ownFields.length; i += 2) { Field f = ownFields[i]; if (f != null) { logFieldMismatch("Local field is missed in stream", f.getType(), f.getDeclaringClass(), f.getName()); missedLocalFields.incrementAndGet(); - if (f.getAnnotation(Default.class) != null) { - defaultFields.add(f); - } + defaultFields.add(new FieldDescriptor(f, null, i / 2)); } } + + return defaultFields.toArray(new FieldDescriptor[0]); } private int findField(FieldDescriptor fd, Field[] ownFields, boolean exactType) { diff --git a/src/one/nio/serial/JsonReader.java b/src/one/nio/serial/JsonReader.java index 12bca05..a301df3 100644 --- a/src/one/nio/serial/JsonReader.java +++ b/src/one/nio/serial/JsonReader.java @@ -353,6 +353,6 @@ private static Number parseNumber(String number) { } } long n = Long.parseLong(number); - return n == (int) n ? Integer.valueOf((int) n) : Long.valueOf(n); + return n == (int) n ? (int) n : (Number) n; } } diff --git a/src/one/nio/serial/gen/DelegateGenerator.java b/src/one/nio/serial/gen/DelegateGenerator.java index 7b6579b..238ceae 100755 --- a/src/one/nio/serial/gen/DelegateGenerator.java +++ b/src/one/nio/serial/gen/DelegateGenerator.java @@ -23,6 +23,7 @@ import one.nio.serial.Repository; import one.nio.serial.SerializeWith; import one.nio.util.Hex; +import one.nio.util.JavaFeatures; import one.nio.util.JavaInternals; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -38,6 +39,7 @@ import java.lang.reflect.ParameterizedType; import java.security.ProtectionDomain; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -81,7 +83,7 @@ private static void defineBootstrapClass(Method m, String classData) throws Refl m.invoke(null, null, null, code, 0, code.length, null, null); } - public static byte[] generate(Class cls, FieldDescriptor[] fds, List defaultFields) { + public static byte[] generate(Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) { String className = "sun/reflect/Delegate" + index.getAndIncrement() + '_' + cls.getSimpleName(); ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); @@ -143,7 +145,7 @@ private static void generateCalcSize(ClassVisitor cv, Class cls, FieldDescriptor mv.visitVarInsn(ALOAD, 2); mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); - emitGetField(mv, ownField, fd.useGetter()); + emitGetSerialField(mv, ownField); emitTypeCast(mv, ownField.getType(), sourceClass); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/CalcSizeStream", "writeObject", "(Ljava/lang/Object;)V"); } @@ -191,7 +193,7 @@ private static void generateWrite(ClassVisitor cv, Class cls, FieldDescriptor[] } else { mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); - emitGetField(mv, ownField, fd.useGetter()); + emitGetSerialField(mv, ownField); emitTypeCast(mv, ownField.getType(), sourceClass); } @@ -203,18 +205,18 @@ private static void generateWrite(ClassVisitor cv, Class cls, FieldDescriptor[] mv.visitEnd(); } - private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, List defaultFields) { + private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "read", "(Lone/nio/serial/DataStream;)Ljava/lang/Object;", null, new String[]{"java/io/IOException", "java/lang/ClassNotFoundException"}); mv.visitCode(); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(NEW, Type.getInternalName(cls)); - mv.visitInsn(DUP); - mv.visitVarInsn(ASTORE, 2); + mv.visitInsn(DUP_X1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "register", "(Ljava/lang/Object;)V"); ArrayList parents = new ArrayList<>(); + boolean isRecord = JavaFeatures.isRecord(cls); for (FieldDescriptor fd : fds) { Field ownField = fd.ownField(); Field parentField = fd.parentField(); @@ -223,55 +225,43 @@ private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] f if (parentField != null && !parents.contains(parentField)) { parents.add(parentField); - mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); - mv.visitVarInsn(ALOAD, 2); - mv.visitLdcInsn(unsafe.objectFieldOffset(parentField)); + if (!isRecord) mv.visitInsn(DUP); mv.visitTypeInsn(NEW, Type.getInternalName(parentField.getType())); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); - mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", "putObject", "(Ljava/lang/Object;JLjava/lang/Object;)V"); + emitPutSerialField(mv, parentField, isRecord, fd); } if (ownField == null) { mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.readMethod(), srcType.readSignature()); mv.visitInsn(srcType.convertTo(FieldType.Void)); - } else if (Modifier.isFinal(ownField.getModifiers()) && !fd.useSetter()) { - FieldType dstType = FieldType.valueOf(ownField.getType()); - mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); - mv.visitVarInsn(ALOAD, 2); - if (parentField != null) emitGetField(mv, parentField); - mv.visitLdcInsn(unsafe.objectFieldOffset(ownField)); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.readMethod(), srcType.readSignature()); - if (srcType == FieldType.Object) emitTypeCast(mv, Object.class, sourceClass); - emitTypeCast(mv, sourceClass, ownField.getType()); - mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", dstType.putMethod(), dstType.putSignature()); } else { - mv.visitVarInsn(ALOAD, 2); + if (!isRecord) mv.visitInsn(DUP); if (parentField != null) emitGetField(mv, parentField); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.readMethod(), srcType.readSignature()); if (srcType == FieldType.Object) emitTypeCast(mv, Object.class, sourceClass); emitTypeCast(mv, sourceClass, ownField.getType()); - emitPutField(mv, ownField, fd.useSetter()); + emitPutSerialField(mv, ownField, isRecord, fd); } } - if (defaultFields != null && !defaultFields.isEmpty()) { - for (Field defaultField : defaultFields) { - setDefaultField(mv, defaultField); - } + for (FieldDescriptor defaultField : defaultFields) { + setDefaultField(mv, defaultField, isRecord); + } + + if (isRecord) { + generateCreateRecord(mv, cls, fds, defaultFields); } Method readObjectMethod = JavaInternals.findMethodRecursively(cls, "readObject", ObjectInputStream.class); if (readObjectMethod != null && !Repository.hasOptions(readObjectMethod.getDeclaringClass(), Repository.SKIP_READ_OBJECT)) { - mv.visitVarInsn(ALOAD, 2); + mv.visitInsn(DUP); mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectInputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectInputStream;"); emitInvoke(mv, readObjectMethod); } - mv.visitVarInsn(ALOAD, 2); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); @@ -345,7 +335,7 @@ private static void generateToJson(ClassVisitor cv, Class cls, FieldDescriptor[] mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); - emitGetField(mv, ownField, fd.useGetter()); + emitGetSerialField(mv, ownField); emitTypeCast(mv, ownField.getType(), sourceClass); switch (srcType) { @@ -375,7 +365,7 @@ private static void generateToJson(ClassVisitor cv, Class cls, FieldDescriptor[] mv.visitEnd(); } - private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds, List defaultFields) { + private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "fromJson", "(Lone/nio/serial/JsonReader;)Ljava/lang/Object;", null, new String[]{"java/io/IOException", "java/lang/ClassNotFoundException"}); mv.visitCode(); @@ -388,23 +378,23 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor // Create instance mv.visitTypeInsn(NEW, Type.getInternalName(cls)); - mv.visitVarInsn(ASTORE, 2); // Prepare a multimap (fieldHash -> fds) for lookupswitch TreeMap fieldHashes = new TreeMap<>(); + boolean isRecord = JavaFeatures.isRecord(cls); for (FieldDescriptor fd : fds) { Field ownField = fd.ownField(); if (ownField != null) { fd.next = fieldHashes.put(ownField.getName().hashCode(), fd); - setDefaultField(mv, ownField); + setDefaultField(mv, fd, isRecord); } } // Initialize default fields before parsing fields from JSON - if (defaultFields != null) { - for (Field defaultField : defaultFields) { - setDefaultField(mv, defaultField); - } + for (FieldDescriptor fd : defaultFields) { + Field ownField = fd.ownField(); + fd.next = fieldHashes.put(ownField.getName().hashCode(), fd); + setDefaultField(mv, fd, isRecord); } // Repeat until '}' @@ -419,7 +409,7 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor mv.visitLabel(loop); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readString", "()Ljava/lang/String;"); - mv.visitVarInsn(ASTORE, 3); + mv.visitVarInsn(ASTORE, 2); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I"); mv.visitInsn(POP); @@ -447,7 +437,7 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor } // Emit lookupswitch for the key hashCode - mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I"); mv.visitLookupSwitchInsn(skipUnknownField, switchKeys, switchLabels); } @@ -461,11 +451,11 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor } do { Label next = new Label(); - mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ALOAD, 2); mv.visitLdcInsn(fd.ownField().getName()); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z"); mv.visitJumpInsn(IFEQ, fd.next == null ? skipUnknownField : next); - generateReadJsonField(mv, fd, parents); + generateReadJsonField(mv, fd, parents, isRecord); mv.visitJumpInsn(GOTO, parseNextField); mv.visitLabel(next); } while ((fd = fd.next) != null); @@ -498,49 +488,41 @@ private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor mv.visitLabel(done); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "read", "()I"); + mv.visitInsn(POP); + + if (isRecord) { + generateCreateRecord(mv, cls, fds, defaultFields); + } Method readObjectMethod = JavaInternals.findMethodRecursively(cls, "readObject", ObjectInputStream.class); if (readObjectMethod != null && !Repository.hasOptions(readObjectMethod.getDeclaringClass(), Repository.SKIP_READ_OBJECT)) { - mv.visitVarInsn(ALOAD, 2); + mv.visitInsn(DUP); mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectInputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectInputStream;"); emitInvoke(mv, readObjectMethod); } - mv.visitVarInsn(ALOAD, 2); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } - private static void generateReadJsonField(MethodVisitor mv, FieldDescriptor fd, List parents) { + private static void generateReadJsonField(MethodVisitor mv, FieldDescriptor fd, List parents, boolean isRecord) { Field ownField = fd.ownField(); Field parentField = fd.parentField(); if (parentField != null && !parents.contains(parentField)) { parents.add(parentField); - mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); - mv.visitVarInsn(ALOAD, 2); - mv.visitLdcInsn(unsafe.objectFieldOffset(parentField)); + if (!isRecord) mv.visitInsn(DUP); mv.visitTypeInsn(NEW, Type.getInternalName(parentField.getType())); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); - mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", "putObject", "(Ljava/lang/Object;JLjava/lang/Object;)V"); + emitPutSerialField(mv, parentField, isRecord, fd); } - if (Modifier.isFinal(ownField.getModifiers()) && !fd.useSetter()) { - FieldType dstType = FieldType.valueOf(ownField.getType()); - mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); - mv.visitVarInsn(ALOAD, 2); - if (parentField != null) emitGetField(mv, parentField); - mv.visitLdcInsn(unsafe.objectFieldOffset(ownField)); - generateReadJsonFieldInternal(mv, ownField); - mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", dstType.putMethod(), dstType.putSignature()); - } else { - mv.visitVarInsn(ALOAD, 2); - if (parentField != null) emitGetField(mv, parentField); - generateReadJsonFieldInternal(mv, ownField); - emitPutField(mv, ownField, fd.useSetter()); - } + if (!isRecord) mv.visitInsn(DUP); + if (parentField != null) emitGetField(mv, parentField); + generateReadJsonFieldInternal(mv, ownField); + emitPutSerialField(mv, ownField, isRecord, fd); } private static void generateReadJsonFieldInternal(MethodVisitor mv, Field ownField) { @@ -620,26 +602,54 @@ private static void generateReadJsonFieldInternal(MethodVisitor mv, Field ownFie mv.visitLabel(done); } + private static void generateCreateRecord(MethodVisitor mv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) { + Class[] args = new Class[fds.length + defaultFields.length]; + for (FieldDescriptor fd : fds) { + if (fd.ownField() != null) { + args[fd.index()] = fd.ownField().getType(); + } + } + for (FieldDescriptor fd : defaultFields) { + args[fd.index()] = fd.ownField().getType(); + } + + int length = args.length; + while (length > 0 && args[length - 1] == null) { + length--; + } + if (length != args.length) { + args = Arrays.copyOf(args, length); + } + + mv.visitInsn(DUP); + for (int i = 0; i < length; i++) { + mv.visitVarInsn(Type.getType(args[i]).getOpcode(ILOAD), 3 + i * 2); + } + + try { + emitInvoke(mv, cls.getConstructor(args)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Cannot find matching canonical constructor for " + cls.getName()); + } + } + private static boolean isConcreteClass(Class cls) { return cls != Object.class && !cls.isInterface(); } - private static void setDefaultField(MethodVisitor mv, Field field) { + private static void setDefaultField(MethodVisitor mv, FieldDescriptor fd, boolean isRecord) { + Field field = fd.ownField(); Default defaultValue = field.getAnnotation(Default.class); - if (defaultValue == null) { + if (defaultValue == null && !isRecord) { return; } - Class fieldType = field.getType(); - if (Modifier.isFinal(field.getModifiers())) { - mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); - mv.visitVarInsn(ALOAD, 2); - mv.visitLdcInsn(unsafe.objectFieldOffset(field)); - } else { - mv.visitVarInsn(ALOAD, 2); - } + Class fieldType = field.getType(); + if (!isRecord) mv.visitInsn(DUP); - if (!defaultValue.method().isEmpty()) { + if (defaultValue == null) { + mv.visitInsn(FieldType.Void.convertTo(FieldType.valueOf(fieldType))); + } else if (!defaultValue.method().isEmpty()) { String methodName = defaultValue.method(); int p = methodName.lastIndexOf('.'); Method m = JavaInternals.findMethod(methodName.substring(0, p), methodName.substring(p + 1)); @@ -659,12 +669,7 @@ private static void setDefaultField(MethodVisitor mv, Field field) { emitDefaultValue(mv, field, fieldType, defaultValue.value()); } - if (Modifier.isFinal(field.getModifiers())) { - FieldType dstType = FieldType.valueOf(fieldType); - mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", dstType.putMethod(), dstType.putSignature()); - } else { - emitPutField(mv, field); - } + emitPutSerialField(mv, field, isRecord, fd); } private static void emitDefaultValue(MethodVisitor mv, Field field, Class fieldType, String value) { @@ -817,11 +822,11 @@ private static void emitTypeCast(MethodVisitor mv, Class src, Class dst) { mv.visitInsn(FieldType.Void.convertTo(FieldType.valueOf(dst))); } - private static void emitGetField(MethodVisitor mv, Field f, boolean useGetter) { - if (useGetter) { - String getter = f.getAnnotation(SerializeWith.class).getter(); + private static void emitGetSerialField(MethodVisitor mv, Field f) { + SerializeWith serializeWith = f.getAnnotation(SerializeWith.class); + if (serializeWith != null && !serializeWith.getter().isEmpty()) { try { - Method m = f.getDeclaringClass().getDeclaredMethod(getter); + Method m = f.getDeclaringClass().getDeclaredMethod(serializeWith.getter()); if (Modifier.isStatic(m.getModifiers()) || m.getReturnType() != f.getType()) { throw new IllegalArgumentException("Incompatible getter method: " + m); } @@ -834,11 +839,16 @@ private static void emitGetField(MethodVisitor mv, Field f, boolean useGetter) { } } - private static void emitPutField(MethodVisitor mv, Field f, boolean useSetter) { - if (useSetter) { - String setter = f.getAnnotation(SerializeWith.class).setter(); + private static void emitPutSerialField(MethodVisitor mv, Field f, boolean isRecord, FieldDescriptor fd) { + if (isRecord) { + mv.visitVarInsn(Type.getType(f.getType()).getOpcode(ISTORE), 3 + fd.index() * 2); + return; + } + + SerializeWith serializeWith = f.getAnnotation(SerializeWith.class); + if (serializeWith != null && !serializeWith.setter().isEmpty()) { try { - Method m = f.getDeclaringClass().getDeclaredMethod(setter, f.getType()); + Method m = f.getDeclaringClass().getDeclaredMethod(serializeWith.setter(), f.getType()); if (Modifier.isStatic(m.getModifiers()) || m.getReturnType() != void.class) { throw new IllegalArgumentException("Incompatible setter method: " + m); } @@ -846,6 +856,10 @@ private static void emitPutField(MethodVisitor mv, Field f, boolean useSetter) { } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Setter method not found", e); } + } else if (Modifier.isFinal(f.getModifiers())) { + FieldType dstType = FieldType.valueOf(f.getType()); + mv.visitLdcInsn(unsafe.objectFieldOffset(f)); + mv.visitMethodInsn(INVOKESTATIC, "one/nio/util/JavaInternals", dstType.putMethod(), dstType.putSignature()); } else { emitPutField(mv, f); } diff --git a/src/one/nio/serial/gen/FieldType.java b/src/one/nio/serial/gen/FieldType.java index 23dc6ee..5da1e27 100755 --- a/src/one/nio/serial/gen/FieldType.java +++ b/src/one/nio/serial/gen/FieldType.java @@ -72,7 +72,7 @@ public String putMethod() { } public String putSignature() { - return "(Ljava/lang/Object;J" + sig + ")V"; + return "(Ljava/lang/Object;" + sig + "J)V"; } public int convertTo(FieldType target) { diff --git a/src/one/nio/util/SpinWait.java b/src/one/nio/util/JavaFeatures.java similarity index 54% rename from src/one/nio/util/SpinWait.java rename to src/one/nio/util/JavaFeatures.java index 785d36a..29ec3ff 100644 --- a/src/one/nio/util/SpinWait.java +++ b/src/one/nio/util/JavaFeatures.java @@ -20,35 +20,52 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -public class SpinWait { +public class JavaFeatures { private static final MethodHandle onSpinWait = getOnSpinWait(); + private static final MethodHandle isRecord = getIsRecord(); private static MethodHandle getOnSpinWait() { try { return MethodHandles.publicLookup().findStatic( Thread.class, "onSpinWait", MethodType.methodType(void.class)); } catch (ReflectiveOperationException e) { - // fall through + return null; } + } - // Older JDK: no Thread.onSpinWait method + private static MethodHandle getIsRecord() { try { - return MethodHandles.lookup().findStatic( - SpinWait.class, "onSpinWait", MethodType.methodType(void.class)); + return MethodHandles.publicLookup().findVirtual( + Class.class, "isRecord", MethodType.methodType(boolean.class)); } catch (ReflectiveOperationException e) { - throw new AssertionError("Should not happen"); + return null; } } - private static void onSpinWait() { - // Fallback implementation + /** + * Calls Thread.onSpinWait() since Java 9; does nothing otherwise + */ + public static void onSpinWait() { + if (onSpinWait != null) { + try { + onSpinWait.invokeExact(); + } catch (Throwable e) { + // Never happens + } + } } - public static void pause() { - try { - onSpinWait.invokeExact(); - } catch (Throwable e) { - // Never happens + /** + * Calls Class.isRecord() since Java 14 preview; returns false otherwise + */ + public static boolean isRecord(Class cls) { + if (isRecord != null) { + try { + return (boolean) isRecord.invokeExact(cls); + } catch (Throwable e) { + // Never happens + } } + return false; } } diff --git a/src/one/nio/util/JavaInternals.java b/src/one/nio/util/JavaInternals.java index 27ff6a8..6353d66 100755 --- a/src/one/nio/util/JavaInternals.java +++ b/src/one/nio/util/JavaInternals.java @@ -173,4 +173,42 @@ public static void setObjectField(Object obj, Class cls, String name, Object public static void uncheckedThrow(Throwable e) throws E { throw (E) e; } + + // Helpers to be called from generated code + + public static void putObject(Object o, Object value, long offset) { + unsafe.putObject(o, offset, value); + } + + public static void putInt(Object o, int value, long offset) { + unsafe.putInt(o, offset, value); + } + + public static void putLong(Object o, long value, long offset) { + unsafe.putLong(o, offset, value); + } + + public static void putBoolean(Object o, boolean value, long offset) { + unsafe.putBoolean(o, offset, value); + } + + public static void putByte(Object o, byte value, long offset) { + unsafe.putByte(o, offset, value); + } + + public static void putShort(Object o, short value, long offset) { + unsafe.putShort(o, offset, value); + } + + public static void putChar(Object o, char value, long offset) { + unsafe.putChar(o, offset, value); + } + + public static void putFloat(Object o, float value, long offset) { + unsafe.putFloat(o, offset, value); + } + + public static void putDouble(Object o, double value, long offset) { + unsafe.putDouble(o, offset, value); + } } diff --git a/test/one/nio/serial/ConversionTest.java b/test/one/nio/serial/ConversionTest.java index cd48237..8c8c7eb 100644 --- a/test/one/nio/serial/ConversionTest.java +++ b/test/one/nio/serial/ConversionTest.java @@ -37,7 +37,7 @@ public void testFieldConversion() throws Exception { byte[] code = DelegateGenerator.generate(ConversionTest.class, new FieldDescriptor[]{ fd("intField", BigInteger.class), fd("longField", BigInteger.class) - }, Collections.emptyList()); + }, new FieldDescriptor[0]); Delegate delegate = BytecodeGenerator.INSTANCE.instantiate(code, Delegate.class); @@ -53,7 +53,7 @@ public void testFieldConversion() throws Exception { private FieldDescriptor fd(String fieldName, Class modifiedType) throws ReflectiveOperationException { FieldDescriptor fd = new FieldDescriptor(fieldName, new TypeDescriptor(modifiedType)); - fd.assignField(getClass().getDeclaredField(fieldName), null); + fd.assignField(getClass().getDeclaredField(fieldName), null, 0); return fd; } } diff --git a/test/one/nio/serial/DefaultFieldsTest.java b/test/one/nio/serial/DefaultFieldsTest.java index 3f3795f..b113414 100755 --- a/test/one/nio/serial/DefaultFieldsTest.java +++ b/test/one/nio/serial/DefaultFieldsTest.java @@ -58,7 +58,11 @@ public class DefaultFieldsTest implements Serializable { @Test public void testDefaultFields() throws Exception { - List defaultFields = Arrays.asList(DefaultFieldsTest.class.getDeclaredFields()); + Field[] ownFields = DefaultFieldsTest.class.getDeclaredFields(); + FieldDescriptor[] defaultFields = new FieldDescriptor[ownFields.length]; + for (int i = 0; i < ownFields.length; i++) { + defaultFields[i] = new FieldDescriptor(ownFields[i], null, i); + } byte[] code = DelegateGenerator.generate(DefaultFieldsTest.class, new FieldDescriptor[0], defaultFields); Delegate delegate = BytecodeGenerator.INSTANCE.instantiate(code, Delegate.class); diff --git a/test/one/nio/serial/SerializationTest.java b/test/one/nio/serial/SerializationTest.java index 3ab8678..9625ae5 100755 --- a/test/one/nio/serial/SerializationTest.java +++ b/test/one/nio/serial/SerializationTest.java @@ -32,11 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class SerializationTest { @@ -269,14 +265,14 @@ public void testInetAddress() throws IOException, ClassNotFoundException { checkSerialize(InetSocketAddress.createUnresolved("www.example.com", 80)); checkSerialize(new InetSocketAddress(21)); - checkSerialize(new InetSocketAddress(InetAddress.getByAddress(new byte[] {8, 8, 8, 8}), 53)); + checkSerialize(new InetSocketAddress(InetAddress.getByAddress(new byte[]{8, 8, 8, 8}), 53)); checkSerialize(new InetSocketAddress("google.com", 443)); } @Test public void testBigDecimal() throws IOException, ClassNotFoundException { checkSerialize(new BigInteger("12345678901234567890")); - checkSerialize(new BigInteger(-1, new byte[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 })); + checkSerialize(new BigInteger(-1, new byte[]{11, 22, 33, 44, 55, 66, 77, 88, 99})); checkSerialize(new BigDecimal(999.999999999)); checkSerialize(new BigDecimal("88888888888888888.88888888888888888888888")); } @@ -290,7 +286,7 @@ public void testStringBuilder() throws IOException, ClassNotFoundException { } private static class ReadObject1 implements Serializable { - private Object[] array = new String[] {"regular", "array"}; + private Object[] array = new String[]{"regular", "array"}; private void readObject(ObjectInputStream in) { for (Object o : array) { @@ -309,7 +305,7 @@ public boolean equals(Object obj) { } private static class ReadObject2 implements Serializable { - private final Object[] array = new String[] {"final", "field"}; + private final Object[] array = new String[]{"final", "field"}; private void readObject(ObjectInputStream in) { for (Object o : array) { @@ -473,4 +469,114 @@ public void testSerializedWrapper() throws IOException, ClassNotFoundException { assertEquals(deserializedArray[0], deserializedArray[1]); assertEquals(deserializedArray[0], deserializedArray[3]); } + + static class GetterSetter implements Serializable { + @SerializeWith(getter = "getN") + int n; + + @SerializeWith(setter = "setS") + String s; + + public int getN() { + return 55; + } + + public void setS(String s) { + this.s = s + s; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetterSetter that = (GetterSetter) o; + return n == that.n && + Objects.equals(s, that.s); + } + + @Override + public String toString() { + return "GetterSetter{" + + "n=" + n + + ", s='" + s + '\'' + + '}'; + } + } + + @Test + public void testSerializeWith() throws IOException, ClassNotFoundException { + GetterSetter gs = new GetterSetter(); + gs.n = 10; + gs.s = "abc"; + + try { + checkSerialize(gs); + fail("Must throw AssertionError"); + } catch (AssertionError e) { + // This is fine + } + + assertEquals(10, gs.n); + + GetterSetter gs2 = (GetterSetter) clone(gs); + assertEquals(55, gs2.n); + assertEquals("abcabc", gs2.s); + + GetterSetter gs3 = (GetterSetter) clone(gs2); + assertEquals(55, gs3.n); + assertEquals("abcabcabcabc", gs3.s); + } + +/* + record Color(String name, int r, int g, int b) implements Serializable { + + public Color(String name, String rgb) { + this(name, hh(rgb, 1, 3), hh(rgb, 3, 5), hh(rgb, 5, 7)); + } + + private static int hh(String s, int from, int to) { + return Integer.parseInt(s.substring(from, to), 16); + } + } + + @Test + public void testRecord() throws IOException, ClassNotFoundException { + checkSerialize(new Color("gold", 255, 215, 0)); + checkSerialize(new Color("skyblue", "#87CEEB")); + + Color pink = (Color) clone(new Color("pink", "#FFC0CB")); + Color pink2 = (Color) clone(new Color("pink", 255, 192, 203)); + assertEquals(pink, pink2); + } + + record Node(Object value, Node left, Node right) implements Serializable { + Node(Object value) { + this(value, null, null); + } + } + + @Test + public void testRecordJson() throws IOException, ClassNotFoundException { + Integer n = 1000; + Node root = new Node("root", + new Node(n, + new Node("leaf_l1"), + new Node("leaf_r1")), + new Node(n, + new Node("leaf_l2"), + new Node("node_r2", + new Node(true), + new Node(false)))); + + checkSerialize(root); + Node root2 = (Node) clone(root); + assertSame(root2.left.value, root2.right.value); + + String s = Json.toJson(root); + Node root3 = Repository.get(Node.class).fromJson(new JsonReader(s.getBytes())); + assertEquals(root, root2); + assertEquals(root, root3); + assertEquals(s, Json.toJson(root2)); + } +*/ }