/* * 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.Repository; import one.nio.util.JavaInternals; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; 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(); public static byte[] generate(Class cls, FieldDescriptor[] fds, List<Field> 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); generateCalcSize(cv, cls, fds); generateWrite(cv, cls, fds); generateRead(cv, cls, fds, defaultFields); generateSkip(cv, fds); generateToJson(cv, fds); cv.visitEnd(); return cv.toByteArray(); } private static void generateConstructor(ClassVisitor cv) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, MAGIC_CLASS, "<init>", "()V"); 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(); Method writeObjectMethod = JavaInternals.findMethodRecursively(cls, "writeObject", ObjectOutputStream.class); if (writeObjectMethod != null && !Repository.hasOptions(writeObjectMethod.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, writeObjectMethod); } 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 (ownField == null) { primitiveFieldsSize++; // 1 byte to encode null reference } else { mv.visitVarInsn(ALOAD, 2); mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); emitGetField(mv, ownField); emitTypeCast(mv, ownField.getType(), sourceClass); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/CalcSizeStream", "writeObject", "(Ljava/lang/Object;)V"); } } 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(); Method writeObjectMethod = JavaInternals.findMethodRecursively(cls, "writeObject", ObjectOutputStream.class); if (writeObjectMethod != null && !Repository.hasOptions(writeObjectMethod.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, writeObjectMethod); } for (FieldDescriptor fd : fds) { Field ownField = fd.ownField(); Class sourceClass = fd.type().resolve(); FieldType srcType = FieldType.valueOf(sourceClass); mv.visitVarInsn(ALOAD, 2); if (ownField == null) { mv.visitInsn(FieldType.Void.convertTo(srcType)); } else { mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); emitGetField(mv, ownField); emitTypeCast(mv, ownField.getType(), sourceClass); } mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", srcType.writeMethod(), srcType.writeSignature()); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } private static void generateRead(ClassVisitor cv, Class cls, FieldDescriptor[] fds, List<Field> 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.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "register", "(Ljava/lang/Object;)V"); ArrayList<Field> parents = new ArrayList<Field>(1); 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); mv.visitFieldInsn(GETSTATIC, "one/nio/util/JavaInternals", "unsafe", "Lsun/misc/Unsafe;"); mv.visitVarInsn(ALOAD, 2); mv.visitLdcInsn(unsafe.objectFieldOffset(parentField)); mv.visitTypeInsn(NEW, Type.getInternalName(parentField.getType())); mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", "putObject", "(Ljava/lang/Object;JLjava/lang/Object;)V"); } 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())) { 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 (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); } } if (defaultFields != null && !defaultFields.isEmpty()) { for (Field defaultField : defaultFields) { String defaultValue = defaultField.getAnnotation(Default.class).value(); putFieldConstant(mv, defaultField, defaultValue); } } Method readObjectMethod = JavaInternals.findMethodRecursively(cls, "readObject", ObjectInputStream.class); if (readObjectMethod != null && !Repository.hasOptions(readObjectMethod.getDeclaringClass(), Repository.SKIP_READ_OBJECT)) { mv.visitVarInsn(ALOAD, 2); 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 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"); mv.visitInsn(POP); skipSize = 0; } mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "readObject", "()Ljava/lang/Object;"); mv.visitInsn(POP); } } if (skipSize > 0) { mv.visitVarInsn(ALOAD, 1); emitInt(mv, skipSize); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/serial/DataStream", "skipBytes", "(I)I"); mv.visitInsn(POP); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } private static void generateToJson(ClassVisitor cv, 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(); boolean firstWritten = false; mv.visitVarInsn(ALOAD, 2); for (FieldDescriptor fd : fds) { Field ownField = fd.ownField(); if (ownField == null) { continue; } String fieldName = "\"" + ownField.getName() + "\":"; mv.visitLdcInsn(firstWritten ? ',' + fieldName : '{' + fieldName); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); firstWritten = true; Class sourceClass = fd.type().resolve(); FieldType srcType = FieldType.valueOf(sourceClass); mv.visitVarInsn(ALOAD, 1); if (fd.parentField() != null) emitGetField(mv, fd.parentField()); emitGetField(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"); mv.visitVarInsn(ALOAD, 2); break; case Char: mv.visitMethodInsn(INVOKESTATIC, "one/nio/serial/Json", "appendChar", "(Ljava/lang/StringBuilder;C)V"); mv.visitVarInsn(ALOAD, 2); break; default: mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", srcType.appendSignature()); } } if (!firstWritten) { emitInt(mv, '{'); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(C)Ljava/lang/StringBuilder;"); } emitInt(mv, '}'); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(C)Ljava/lang/StringBuilder;"); mv.visitInsn(POP); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } private static void putFieldConstant(MethodVisitor mv, Field field, String value) { 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(); FieldType dstType = FieldType.valueOf(fieldType); switch (dstType) { case Int: case Byte: case Short: emitInt(mv, Integer.decode(value)); break; case Long: emitLong(mv, Long.decode(value)); break; case Boolean: emitInt(mv, Boolean.parseBoolean(value) ? 1 : 0); break; case Char: emitInt(mv, value.length() == 1 ? value.charAt(0) : Integer.decode(value)); break; case Float: emitFloat(mv, Float.parseFloat(value)); break; case Double: emitDouble(mv, Double.parseDouble(value)); break; default: 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;"); } else if (fieldType == Class.class) { try { mv.visitLdcInsn(Class.forName(value, false, INSTANCE)); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot set default value \"" + value + "\" to " + field, e); } } else { try { Method valueOf = fieldType.getMethod("valueOf", String.class); if (!Modifier.isStatic(valueOf.getModifiers()) || valueOf.getReturnType() != fieldType) { throw new NoSuchMethodException("valueOf(String) is not found in class " + fieldType.getName()); } mv.visitLdcInsn(value); emitInvoke(mv, valueOf); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot set default value \"" + value + "\" to " + field, e); } } } if (Modifier.isFinal(field.getModifiers())) { mv.visitMethodInsn(INVOKESPECIAL, "sun/misc/Unsafe", dstType.putMethod(), dstType.putSignature()); } else { emitPutField(mv, field); } } private static void emitTypeCast(MethodVisitor mv, Class<?> src, Class<?> dst) { // Trivial case if (src == dst || dst.isAssignableFrom(src)) { return; } // Type widening if (src.isAssignableFrom(dst)) { mv.visitTypeInsn(CHECKCAST, Type.getInternalName(dst)); 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()) { 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); return; } // Number -> Number if (src.getSuperclass() == Number.class && dst.getSuperclass() == Number.class) { for (Method m : dst.getMethods()) { if (m.getParameterTypes().length == 0 && 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) { 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); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(dst), "valueOf", valueOfSignature); return; } } } } // Dst.valueOf(src) try { Method m = dst.getMethod("valueOf", src); if (Modifier.isStatic(m.getModifiers()) && m.getReturnType() == dst) { emitInvoke(mv, m); return; } } catch (NoSuchMethodException e) { // continue } // dst = src.someMethod() for (Method m : src.getMethods()) { if (!Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == dst) { emitInvoke(mv, m); 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))); } }