one.nio.serial.gen.DelegateGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of one-nio Show documentation
Show all versions of one-nio Show documentation
Unconventional Java I/O library
The newest version!
/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* Licensed 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 one.nio.serial.gen;
import one.nio.gen.BytecodeGenerator;
import one.nio.serial.Default;
import one.nio.serial.FieldDescriptor;
import one.nio.serial.JsonName;
import one.nio.serial.NotSerial;
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 one.nio.util.MethodHandlesReflection;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import static one.nio.util.JavaInternals.unsafe;
public class DelegateGenerator extends BytecodeGenerator {
private static final AtomicInteger index = new AtomicInteger();
// Allows to bypass security checks when accessing private members of other classes
private static final String MAGIC_CLASS = "sun/reflect/MagicAccessorImpl";
// In JDK 9+ there is no more sun.reflect.MagicAccessorImpl class.
// Instead there is package private jdk.internal.reflect.MagicAccessorImpl, which is not visible
// by application classes. We abuse ClassLoaded private API to inject a publicly visible bridge
// using the bootstrap ClassLoader.
// ¯\_(ツ)_/¯
static {
if (JavaInternals.hasModules()) {
try {
Method m = JavaInternals.getMethod(ClassLoader.class, "defineClass1", ClassLoader.class, String.class,
byte[].class, int.class, int.class, ProtectionDomain.class, String.class);
if (m == null) {
throw new NoSuchMethodException("ClassLoader.defineClass1");
}
// public jdk.internal.reflect.MagicAccessorBridge extends jdk.internal.reflect.MagicAccessorImpl
defineBootstrapClass(m, "cafebabe00000033000a0a000300070700080700090100063c696e69743e010003282956010004436f64650c000400050100286a646b2f696e7465726e616c2f7265666c6563742f4d616769634163636573736f724272696467650100266a646b2f696e7465726e616c2f7265666c6563742f4d616769634163636573736f72496d706c042100020003000000000001000100040005000100060000001100010001000000052ab70001b1000000000000");
// public sun.reflect.MagicAccessorImpl extends jdk.internal.reflect.MagicAccessorBridge
defineBootstrapClass(m, "cafebabe00000033000a0a000300070700080700090100063c696e69743e010003282956010004436f64650c0004000501001d73756e2f7265666c6563742f4d616769634163636573736f72496d706c0100286a646b2f696e7465726e616c2f7265666c6563742f4d616769634163636573736f72427269646765042100020003000000000001000100040005000100060000001100010001000000052ab70001b1000000000000");
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
// Defines a new class by the bootstrap ClassLoader
private static void defineBootstrapClass(Method m, String classData) throws ReflectiveOperationException {
byte[] code = Hex.parseBytes(classData);
m.invoke(null, null, null, code, 0, code.length, null, null);
}
public static Delegate instantiate(Class cls, FieldDescriptor[] fds, byte[] code) {
Map fieldsMap = null;
if (Repository.hasOptions(cls, Repository.PROVIDE_GET_FIELD)) {
fieldsMap = new HashMap<>(fds.length, 1);
for (FieldDescriptor fd : fds) {
Field field = fd.ownField();
if (field != null) {
fieldsMap.put(field.getName(), field);
JavaInternals.setAccessible(field);
}
}
}
try {
return (Delegate) BytecodeGenerator.INSTANCE.defineClass(code)
.getDeclaredConstructor(Map.class)
.newInstance(fieldsMap);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot instantiate class", e);
}
}
public static Delegate instantiate(Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields) {
return instantiate(cls, fds, generate(cls, fds, 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);
cv.visit(V1_6, ACC_PUBLIC | ACC_FINAL, className, null, MAGIC_CLASS,
new String[]{"one/nio/serial/gen/Delegate"});
generateConstructor(cv, className);
generateCalcSize(cv, cls, fds);
generateWrite(cv, cls, fds);
generateRead(cv, cls, fds, defaultFields, className);
generateSkip(cv, fds);
generateToJson(cv, cls, fds);
generateFromJson(cv, cls, fds, defaultFields, className);
cv.visitEnd();
return cv.toByteArray();
}
private static void generateConstructor(ClassVisitor cv, String className) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "", "(Ljava/util/Map;)V", null, null);
cv.visitField(ACC_PRIVATE | ACC_FINAL, "fields", "Ljava/util/Map;", null, null).visitEnd();
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, MAGIC_CLASS, "", "()V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, className, "fields", "Ljava/util/Map;");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void generateCalcSize(ClassVisitor cv, Class cls, FieldDescriptor[] fds) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "calcSize", "(Ljava/lang/Object;Lone/nio/serial/CalcSizeStream;)V",
null, new String[]{"java/io/IOException"});
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
emitTypeCast(mv, Object.class, cls);
mv.visitVarInsn(ASTORE, 1);
emitWriteObject(cls, mv);
int primitiveFieldsSize = 0;
for (FieldDescriptor fd : fds) {
Field ownField = fd.ownField();
Class sourceClass = fd.type().resolve();
FieldType srcType = FieldType.valueOf(sourceClass);
if (srcType != FieldType.Object) {
primitiveFieldsSize += srcType.dataSize;
} else if (isNotSerial(ownField)) {
primitiveFieldsSize++; // 1 byte to encode null reference
} else {
mv.visitVarInsn(ALOAD, 2);
mv.visitVarInsn(ALOAD, 1);
if (fd.parentField() != null) emitGetField(mv, fd.parentField());
emitGetSerialField(mv, ownField);
emitTypeCast(mv, ownField.getType(), sourceClass);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/CalcSizeStream", "writeObject", "(Ljava/lang/Object;)V", false);
}
}
if (primitiveFieldsSize != 0) {
mv.visitVarInsn(ALOAD, 2);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, "one/nio/serial/CalcSizeStream", "count", "I");
emitInt(mv, primitiveFieldsSize);
mv.visitInsn(IADD);
mv.visitFieldInsn(PUTFIELD, "one/nio/serial/CalcSizeStream", "count", "I");
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void generateWrite(ClassVisitor cv, Class cls, FieldDescriptor[] fds) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "write", "(Ljava/lang/Object;Lone/nio/serial/DataStream;)V",
null, new String[]{"java/io/IOException"});
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
emitTypeCast(mv, Object.class, cls);
mv.visitVarInsn(ASTORE, 1);
emitWriteObject(cls, mv);
for (FieldDescriptor fd : fds) {
Field ownField = fd.ownField();
Class sourceClass = fd.type().resolve();
FieldType srcType = FieldType.valueOf(sourceClass);
mv.visitVarInsn(ALOAD, 2);
if (isNotSerial(ownField)) {
mv.visitInsn(FieldType.Void.convertTo(srcType));
} else {
mv.visitVarInsn(ALOAD, 1);
if (fd.parentField() != null) emitGetField(mv, fd.parentField());
emitGetSerialField(mv, ownField);
emitTypeCast(mv, ownField.getType(), sourceClass);
}
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.writeMethod(), srcType.writeSignature(), false);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void emitWriteObject(Class cls, MethodVisitor mv) {
MethodType methodType = MethodType.methodType(void.class, ObjectOutputStream.class);
MethodHandleInfo m = MethodHandlesReflection.findInstanceMethod(cls, "writeObject", methodType);
if (m != null && !Repository.hasOptions(m.getDeclaringClass(), Repository.SKIP_WRITE_OBJECT)) {
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectOutputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectOutputStream;");
emitInvoke(mv, m);
}
}
private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields, String className) {
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_X1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "register", "(Ljava/lang/Object;)V", false);
ArrayList parents = new ArrayList<>();
boolean isRecord = JavaFeatures.isRecord(cls);
for (FieldDescriptor fd : fds) {
Field ownField = fd.ownField();
Field parentField = fd.parentField();
Class sourceClass = fd.type().resolve();
FieldType srcType = FieldType.valueOf(sourceClass);
if (parentField != null && !parents.contains(parentField)) {
parents.add(parentField);
if (!isRecord) mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, Type.getInternalName(parentField.getType()));
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
emitPutSerialField(mv, parentField, isRecord, fd);
}
if (isNotSerial(ownField)) {
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.readMethod(), srcType.readSignature(), false);
mv.visitInsn(srcType.convertTo(FieldType.Void));
} else {
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(), false);
if (srcType == FieldType.Object) emitTypeCast(mv, Object.class, sourceClass);
emitTypeCast(mv, sourceClass, ownField.getType());
emitPutSerialField(mv, ownField, isRecord, fd);
}
}
for (FieldDescriptor defaultField : defaultFields) {
setDefaultField(mv, defaultField, isRecord);
}
if (isRecord) {
generateCreateRecord(mv, cls, fds, defaultFields);
}
emitReadObject(cls, mv, className);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void emitReadObject(Class cls, MethodVisitor mv, String className) {
MethodType methodType = MethodType.methodType(void.class, ObjectInputStream.class);
MethodHandleInfo m = MethodHandlesReflection.findInstanceMethod(cls, "readObject", methodType);
if (m != null && !Repository.hasOptions(m.getDeclaringClass(), Repository.SKIP_READ_OBJECT)) {
mv.visitInsn(DUP);
if (!Repository.hasOptions(m.getDeclaringClass(), Repository.PROVIDE_GET_FIELD)) {
mv.visitFieldInsn(GETSTATIC, "one/nio/serial/gen/NullObjectInputStream", "INSTANCE", "Lone/nio/serial/gen/NullObjectInputStream;");
} else {
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "one/nio/serial/gen/GetFieldInputStream");
mv.visitInsn(DUP_X1);
mv.visitInsn(SWAP);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, "fields", "Ljava/util/Map;");
mv.visitMethodInsn(INVOKESPECIAL, "one/nio/serial/gen/GetFieldInputStream", "", "(Ljava/lang/Object;Ljava/util/Map;)V", false);
}
emitInvoke(mv, m);
}
}
private static void generateSkip(ClassVisitor cv, FieldDescriptor[] fds) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "skip", "(Lone/nio/serial/DataStream;)V",
null, new String[]{"java/io/IOException", "java/lang/ClassNotFoundException"});
mv.visitCode();
int skipSize = 0;
for (FieldDescriptor fd : fds) {
Class sourceClass = fd.type().resolve();
FieldType srcType = FieldType.valueOf(sourceClass);
if (srcType != FieldType.Object) {
skipSize += srcType.dataSize;
} else {
if (skipSize > 0) {
mv.visitVarInsn(ALOAD, 1);
emitInt(mv, skipSize);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "skipBytes", "(I)I", false);
mv.visitInsn(POP);
skipSize = 0;
}
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "readObject", "()Ljava/lang/Object;", false);
mv.visitInsn(POP);
}
}
if (skipSize > 0) {
mv.visitVarInsn(ALOAD, 1);
emitInt(mv, skipSize);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "skipBytes", "(I)I", false);
mv.visitInsn(POP);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void generateToJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds) {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "toJson", "(Ljava/lang/Object;Ljava/lang/StringBuilder;)V",
null, new String[]{"java/io/IOException"});
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
emitTypeCast(mv, Object.class, cls);
mv.visitVarInsn(ASTORE, 1);
boolean firstWritten = false;
mv.visitVarInsn(ALOAD, 2);
for (FieldDescriptor fd : fds) {
Field ownField = fd.ownField();
if (isNotSerial(ownField)) {
continue;
}
JsonName jsonName = ownField.getAnnotation(JsonName.class);
String fieldName = jsonName != null ? jsonName.value() : ownField.getName();
mv.visitLdcInsn((firstWritten ? ',' : '{') + "\"" + fieldName + "\":");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
firstWritten = true;
Class sourceClass = fd.type().resolve();
FieldType srcType = FieldType.valueOf(sourceClass);
mv.visitVarInsn(ALOAD, 1);
if (fd.parentField() != null) emitGetField(mv, fd.parentField());
emitGetSerialField(mv, ownField);
emitTypeCast(mv, ownField.getType(), sourceClass);
switch (srcType) {
case Object:
mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/Json", "appendObject", "(Ljava/lang/StringBuilder;Ljava/lang/Object;)V", false);
mv.visitVarInsn(ALOAD, 2);
break;
case Char:
mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/Json", "appendChar", "(Ljava/lang/StringBuilder;C)V", false);
mv.visitVarInsn(ALOAD, 2);
break;
default:
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", srcType.appendSignature(), false);
}
}
if (!firstWritten) {
emitInt(mv, '{');
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(C)Ljava/lang/StringBuilder;", false);
}
emitInt(mv, '}');
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(C)Ljava/lang/StringBuilder;", false);
mv.visitInsn(POP);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void generateFromJson(ClassVisitor cv, Class cls, FieldDescriptor[] fds, FieldDescriptor[] defaultFields, String className) {
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();
// Find opening '{'
mv.visitVarInsn(ALOAD, 1);
mv.visitIntInsn(BIPUSH, '{');
mv.visitLdcInsn("Expected object");
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "expect", "(ILjava/lang/String;)V", false);
// Create instance
mv.visitTypeInsn(NEW, Type.getInternalName(cls));
// 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 (isNotSerial(ownField)) {
continue;
}
fd.next = fieldHashes.put(ownField.getName().hashCode(), fd);
setDefaultField(mv, fd, isRecord);
}
// Initialize default fields before parsing fields from JSON
for (FieldDescriptor fd : defaultFields) {
Field ownField = fd.ownField();
fd.next = fieldHashes.put(ownField.getName().hashCode(), fd);
setDefaultField(mv, fd, isRecord);
}
// Repeat until '}'
Label done = new Label();
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I", false);
mv.visitIntInsn(BIPUSH, '}');
mv.visitJumpInsn(IF_ICMPEQ, done);
// Read key
Label loop = new Label();
mv.visitLabel(loop);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readString", "()Ljava/lang/String;", false);
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I", false);
mv.visitInsn(POP);
mv.visitVarInsn(ALOAD, 1);
mv.visitIntInsn(BIPUSH, ':');
mv.visitLdcInsn("Expected key-value pair");
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "expect", "(ILjava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I", false);
mv.visitInsn(POP);
// Prepare labels for lookupswitch
Label parseNextField = new Label();
Label skipUnknownField = new Label();
Label[] switchLabels = new Label[fieldHashes.size()];
// Use lookupswitch only if there are multiple hashes
if (switchLabels.length > 1) {
int[] switchKeys = new int[switchLabels.length];
int i = 0;
for (Integer key : fieldHashes.keySet()) {
switchKeys[i] = key;
switchLabels[i] = new Label();
i++;
}
// Emit lookupswitch for the key hashCode
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false);
mv.visitLookupSwitchInsn(skipUnknownField, switchKeys, switchLabels);
}
// Go through lookupswitch labels
ArrayList parents = new ArrayList<>();
int i = 0;
for (FieldDescriptor fd : fieldHashes.values()) {
if (switchLabels[i] != null) {
mv.visitLabel(switchLabels[i++]);
}
do {
Label next = new Label();
mv.visitVarInsn(ALOAD, 2);
mv.visitLdcInsn(fd.ownField().getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
mv.visitJumpInsn(IFEQ, fd.next == null ? skipUnknownField : next);
generateReadJsonField(mv, fd, parents, isRecord);
mv.visitJumpInsn(GOTO, parseNextField);
mv.visitLabel(next);
} while ((fd = fd.next) != null);
}
// Read and discard the value of unknown field
mv.visitLabel(skipUnknownField);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readObject", "()Ljava/lang/Object;", false);
mv.visitInsn(POP);
// Find '}' for the end or ',' for the next field
mv.visitLabel(parseNextField);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I", false);
mv.visitIntInsn(BIPUSH, '}');
mv.visitJumpInsn(IF_ICMPEQ, done);
// Read ','
mv.visitVarInsn(ALOAD, 1);
mv.visitIntInsn(BIPUSH, ',');
mv.visitLdcInsn("Unexpected end of object");
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "expect", "(ILjava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "skipWhitespace", "()I", false);
mv.visitInsn(POP);
mv.visitJumpInsn(GOTO, loop);
// Finish deserialization and return constructed object
mv.visitLabel(done);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "read", "()I", false);
mv.visitInsn(POP);
if (isRecord) {
generateCreateRecord(mv, cls, fds, defaultFields);
}
emitReadObject(cls, mv, className);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
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);
if (!isRecord) mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, Type.getInternalName(parentField.getType()));
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
emitPutSerialField(mv, parentField, isRecord, fd);
}
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) {
Class fieldClass = ownField.getType();
if (fieldClass.isPrimitive()) {
FieldType fieldType = FieldType.valueOf(fieldClass);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", fieldType.readMethod(), fieldType.readSignature(), false);
return;
}
// Check if the next literal is "null"
Label done = new Label();
Label notNull = new Label();
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, "one/nio/serial/JsonReader", "next", "I");
emitInt(mv, 'n');
mv.visitJumpInsn(IF_ICMPNE, notNull);
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readNull", "()Ljava/lang/Object;", false);
mv.visitJumpInsn(GOTO, done);
mv.visitLabel(notNull);
readObject:
if (fieldClass == String.class) {
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readString", "()Ljava/lang/String;", false);
} else if (fieldClass.isArray() && !fieldClass.getComponentType().isPrimitive()) {
Class componentType = fieldClass.getComponentType();
if (isConcreteClass(componentType)) {
mv.visitLdcInsn(Type.getType(componentType));
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readArray", "(Ljava/lang/reflect/Type;)Ljava/util/ArrayList;", false);
emitInt(mv, 0);
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(componentType));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(fieldClass));
} else {
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readArray", "()Ljava/util/ArrayList;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "toArray", "()[Ljava/lang/Object;", false);
emitTypeCast(mv, Object[].class, fieldClass);
}
} else if (Collection.class.isAssignableFrom(fieldClass)) {
java.lang.reflect.Type genericType = ownField.getGenericType();
if (genericType instanceof ParameterizedType) {
java.lang.reflect.Type[] args = ((ParameterizedType) genericType).getActualTypeArguments();
if (args.length > 0 && args[0] instanceof Class && isConcreteClass((Class) args[0])) {
mv.visitLdcInsn(Type.getType((Class) args[0]));
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readArray", "(Ljava/lang/reflect/Type;)Ljava/util/ArrayList;", false);
emitTypeCast(mv, ArrayList.class, fieldClass);
break readObject;
}
}
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readArray", "()Ljava/util/ArrayList;", false);
emitTypeCast(mv, ArrayList.class, fieldClass);
} else if (Map.class.isAssignableFrom(fieldClass)) {
java.lang.reflect.Type genericType = ownField.getGenericType();
if (genericType instanceof ParameterizedType) {
java.lang.reflect.Type[] args = ((ParameterizedType) genericType).getActualTypeArguments();
if (args.length > 1 && args[0] instanceof Class && args[1] instanceof Class && isConcreteClass((Class) args[1])) {
mv.visitLdcInsn(Type.getType((Class) args[0]));
mv.visitLdcInsn(Type.getType((Class) args[1]));
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readMap", "(Ljava/lang/Class;Ljava/lang/reflect/Type;)Ljava/util/Map;", false);
emitTypeCast(mv, Map.class, fieldClass);
break readObject;
}
}
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readMap", "()Ljava/util/Map;", false);
emitTypeCast(mv, Map.class, fieldClass);
} else if (isConcreteClass(fieldClass)) {
mv.visitLdcInsn(Type.getType(fieldClass));
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readObject", "(Ljava/lang/Class;)Ljava/lang/Object;", false);
emitTypeCast(mv, Object.class, fieldClass);
} else {
mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/JsonReader", "readObject", "()Ljava/lang/Object;", false);
emitTypeCast(mv, Object.class, fieldClass);
}
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.getDeclaredConstructor(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 boolean isNotSerial(Field field) {
return field == null || field.getAnnotation(NotSerial.class) != null;
}
private static void setDefaultField(MethodVisitor mv, FieldDescriptor fd, boolean isRecord) {
Field field = fd.ownField();
Default defaultValue = field.getAnnotation(Default.class);
if (defaultValue == null && !isRecord) {
return;
}
Class> fieldType = field.getType();
if (!isRecord) mv.visitInsn(DUP);
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));
if (m == null || !Modifier.isStatic(m.getModifiers()) || !fieldType.isAssignableFrom(m.getReturnType())) {
throw new IllegalArgumentException("Invalid default initializer " + methodName + " for field " + field);
}
emitInvoke(mv, m);
} else if (!defaultValue.field().isEmpty()) {
String fieldName = defaultValue.field();
int p = fieldName.lastIndexOf('.');
Field f = JavaInternals.findField(fieldName.substring(0, p), fieldName.substring(p + 1));
if (f == null || !Modifier.isStatic(f.getModifiers()) || !fieldType.isAssignableFrom(f.getType())) {
throw new IllegalArgumentException("Invalid default initializer " + fieldName + " for field " + field);
}
emitGetField(mv, f);
} else {
emitDefaultValue(mv, field, fieldType, defaultValue.value());
}
emitPutSerialField(mv, field, isRecord, fd);
}
private static void emitDefaultValue(MethodVisitor mv, Field field, Class> fieldType, String value) {
switch (FieldType.valueOf(fieldType)) {
case Int:
case Byte:
case Short:
emitInt(mv, Integer.decode(value));
return;
case Long:
emitLong(mv, Long.decode(value));
return;
case Boolean:
emitInt(mv, Boolean.parseBoolean(value) ? 1 : 0);
return;
case Char:
emitInt(mv, value.length() == 1 ? value.charAt(0) : Integer.decode(value));
return;
case Float:
emitFloat(mv, Float.parseFloat(value));
return;
case Double:
emitDouble(mv, Double.parseDouble(value));
return;
}
if (fieldType == String.class) {
mv.visitLdcInsn(value);
} else if (fieldType == Character.class) {
emitInt(mv, value.length() == 1 ? value.charAt(0) : Integer.decode(value));
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
} else if (fieldType == Class.class) {
mv.visitLdcInsn(Type.getObjectType(value.replace('.', '/')));
} else {
try {
MethodHandleInfo valueOf = MethodHandlesReflection.findStaticMethodOrThrow(fieldType, "valueOf", MethodType.methodType(fieldType, String.class));
mv.visitLdcInsn(value);
emitInvoke(mv, valueOf);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalArgumentException("Cannot set default value \"" + value + "\" to " + field, e);
}
}
}
private static void emitTypeCast(MethodVisitor mv, Class> src, Class> dst) {
// Trivial case
if (src == dst || dst.isAssignableFrom(src)) {
return;
}
// Primitive -> Primitive
if (src.isPrimitive() && dst.isPrimitive()) {
FieldType srcType = FieldType.valueOf(src);
FieldType dstType = FieldType.valueOf(dst);
for (int opcode = srcType.convertTo(dstType); opcode != 0; opcode >>>= 8) {
mv.visitInsn(opcode & 0xff);
}
return;
}
// A[] -> B[]
if (src.isArray() && dst.isArray() && src.getComponentType().isPrimitive() == dst.getComponentType().isPrimitive()) {
Label isNull = emitNullGuard(mv, dst);
mv.visitInsn(DUP);
mv.visitInsn(ARRAYLENGTH);
Class dstComponent = dst.getComponentType();
String copySig;
if (dstComponent.isPrimitive()) {
mv.visitIntInsn(NEWARRAY, FieldType.valueOf(dstComponent).bytecodeType);
copySig = "(" + Type.getDescriptor(src) + Type.getDescriptor(dst) + ")V";
} else {
mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(dstComponent));
copySig = "([Ljava/lang/Object;[Ljava/lang/Object;)V";
}
mv.visitInsn(DUP_X1);
mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/gen/ArrayCopy", "copy", copySig, false);
mv.visitLabel(isNull);
return;
}
// Type widening
if (src.isAssignableFrom(dst)) {
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(dst));
return;
}
// Number -> Number
if (src.getSuperclass() == Number.class && dst.getSuperclass() == Number.class) {
for (Method m : dst.getMethods()) {
if (m.getParameterTypes().length == 1 && m.getReturnType() == dst &&
Modifier.isStatic(m.getModifiers()) && "valueOf".equals(m.getName())) {
Class param = m.getParameterTypes()[0];
if (param.isPrimitive() && param != boolean.class && param != char.class) {
Label isNull = emitNullGuard(mv, dst);
String valueMethod = param.getName() + "Value";
String valueSignature = "()" + Type.getDescriptor(param);
String valueOfSignature = "(" + Type.getDescriptor(param) + ")" + Type.getDescriptor(dst);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", valueMethod, valueSignature, false);
mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(dst), "valueOf", valueOfSignature, false);
mv.visitLabel(isNull);
return;
}
}
}
}
// Collection -> List, Set
if (Collection.class.isAssignableFrom(src)) {
Class target = dst.isAssignableFrom(ArrayList.class) ? ArrayList.class :
dst.isAssignableFrom(HashSet.class) ? HashSet.class : null;
if (target != null) {
Label isNull = emitNullGuard(mv, dst);
mv.visitTypeInsn(NEW, Type.getInternalName(target));
mv.visitInsn(DUP_X1);
mv.visitInsn(SWAP);
mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(target), "", "(Ljava/util/Collection;)V", false);
mv.visitLabel(isNull);
return;
}
}
// Dst.valueOf(src)
MethodHandleInfo valueOf = MethodHandlesReflection.findStaticMethod(dst, "valueOf", MethodType.methodType(dst, src));
if (valueOf != null) {
emitInvoke(mv, valueOf);
return;
}
// dst = src.someMethod()
for (Method m : src.getMethods()) {
if (!Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == dst) {
Label isNull = emitNullGuard(mv, dst);
emitInvoke(mv, m);
mv.visitLabel(isNull);
return;
}
}
// The types are not convertible, just leave the default value
mv.visitInsn(FieldType.valueOf(src).convertTo(FieldType.Void));
mv.visitInsn(FieldType.Void.convertTo(FieldType.valueOf(dst)));
}
private static void emitGetSerialField(MethodVisitor mv, Field f) {
SerializeWith serializeWith = f.getAnnotation(SerializeWith.class);
if (serializeWith != null && !serializeWith.getter().isEmpty()) {
try {
MethodHandleInfo m = MethodHandlesReflection.findInstanceMethodOrThrow(f.getDeclaringClass(), serializeWith.getter(), MethodType.methodType(f.getType()));
emitInvoke(mv, m);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Getter method not found", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Incompatible getter method", e);
}
} else {
emitGetField(mv, f);
}
}
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 {
MethodHandleInfo m = MethodHandlesReflection.findInstanceMethodOrThrow(f.getDeclaringClass(), serializeWith.setter(), MethodType.methodType(void.class, f.getType()));
emitInvoke(mv, m);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Setter method not found", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Incompatible setter method", 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(), false);
} else {
emitPutField(mv, f);
}
}
private static Label emitNullGuard(MethodVisitor mv, Class> dst) {
Label isNull = new Label();
Label nonNull = new Label();
mv.visitInsn(DUP);
mv.visitJumpInsn(IFNONNULL, nonNull);
mv.visitInsn(POP);
mv.visitInsn(FieldType.Void.convertTo(FieldType.valueOf(dst)));
mv.visitJumpInsn(GOTO, isNull);
mv.visitLabel(nonNull);
return isNull;
}
}