/*
* The MIT License (MIT)
*
* Copyright (c) 2016. Diorite (by Bartłomiej Mazur (aka GotoFinal))
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.diorite.inject.impl.controller;
import java.lang.instrument.ClassFileTransformer;
import java.util.function.Predicate;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.diorite.inject.Injection;
import org.diorite.inject.impl.controller.TransformerInjectTracker.PlaceholderType;
import org.diorite.inject.impl.data.InjectValueData;
import org.diorite.inject.impl.utils.AsmUtils;
import org.diorite.inject.impl.utils.Constants;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodDescription.InDefinedShape;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeDescription.Generic;
import net.bytebuddy.implementation.Implementation.Context;
import net.bytebuddy.pool.TypePool;
final class TransformerInvokerGenerator implements ClassFileTransformer, Opcodes
{
public static final String INJECTOR_CLASS = Constants.INJECTOR.getInternalName();
public static final String INJECTOR_FIELD = "injectField";
public static final String INJECTOR_FIELD_DESC = "(Ljava/lang/Object;IIZ)Ljava/lang/Object;";
public static final String INJECTOR_METHOD = "injectMethod";
public static final String INJECTOR_METHOD_DESC = "(Ljava/lang/Object;IIIZ)Ljava/lang/Object;";
public static final String GENERATED_PREFIX = Injection.class.getPackage().getName() + ".generated.invokers";
public static final Object[] STACK = {};
public static final int HASHCODE_MULTI = 127;
private static final class AnnotationImplementationVisitor implements AsmVisitorWrapper
{
private final TypeDescription clazz;
private AnnotationImplementationVisitor(TypeDescription clazz)
{
this.clazz = clazz;
}
@Override
public int mergeWriter(int flags)
{
return flags | ClassWriter.COMPUTE_MAXS;
}
@Override
public int mergeReader(int flags)
{
return flags;
}
@Override
public ClassVisitor wrap(TypeDescription typeDescription, ClassVisitor cv, Context context, TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fieldList, MethodList<?> methodList, int i, int i1)
{
// public void visit(int version, int modifiers, String name, String signature, String superName, String[] interfaces) {
cv.visit(ClassFileVersion.JAVA_V9.getMinorMajorVersion(), typeDescription.getModifiers(), typeDescription.getInternalName(), null,
typeDescription.getSuperClass().asErasure().getInternalName(), typeDescription.getInterfaces().asErasures().toInternalNames());
TypeDescription clazz = this.clazz;
String internalName = clazz.getInternalName();
String descriptor = clazz.getDescriptor();
MethodList<InDefinedShape> declaredMethods = clazz.getDeclaredMethods();
int methodsSize = declaredMethods.size();
String implName = GENERATED_PREFIX + "." + clazz.getName();
String internalImplName = GENERATED_PREFIX.replace('.', '/') + "/" + internalName;
String descriptorImplName = "L" + GENERATED_PREFIX.replace('.', '/') + "/" + internalName + ";";
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cv.visitEnd();
return cv;
}
}
// public static void test(ClassData classData, byte[] bytecode)
// {
// ClassReader cr = new ClassReader(bytecode);
// ClassNode node = new ClassNode(Opcodes.ASM6);
// cr.accept(node, 0);
//
// classData.getMethods()
// List<MethodNode> methods = node.methods;
// for (MethodNode method : methods)
// {
// if (method.desc)
// }
// }
public static int generateFieldInjection(ControllerClassData classData, ControllerFieldData<?> fieldData, MethodNode mv, int lineNumber,
PlaceholderType placeholderType)
{
AbstractInsnNode[] result = new AbstractInsnNode[2];
FieldDescription.InDefinedShape member = fieldData.getMember();
TypeDescription fieldType = member.getType().asErasure();
boolean isStatic = member.isStatic();
lineNumber = AsmUtils.printLineNumber(mv, lineNumber);
if (isStatic)
{
mv.visitInsn(ACONST_NULL);
}
else
{
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 0);
}
AsmUtils.storeInt(mv, classData.getIndex());
AsmUtils.storeInt(mv, fieldData.getIndex());
switch (placeholderType)
{
case INVALID:
case UNKNOWN:
default:
throw new IllegalStateException("Can't generate injection for invalid placeholders.");
case NONNULL:
mv.visitInsn(ICONST_1);
break;
case NULLABLE:
mv.visitInsn(ICONST_0);
break;
}
mv.visitMethodInsn(INVOKESTATIC, INJECTOR_CLASS, INJECTOR_FIELD, INJECTOR_FIELD_DESC, false);
return lineNumber;
}
public static int printMethods(MethodNode mv, String clazz, Iterable<String> methods, Predicate<String> isStatic, int lineNumber)
{
for (String method : methods)
{
lineNumber = printMethod(mv, clazz, method, isStatic.test(method), lineNumber);
}
return lineNumber;
}
public static int printMethods(MethodNode mv, String clazz, Iterable<String> methods, boolean isStatic, int lineNumber)
{
for (String method : methods)
{
lineNumber = printMethod(mv, clazz, method, isStatic, lineNumber);
}
return lineNumber;
}
public static int printMethod(MethodNode mv, String clazz, String method, boolean isStatic, int lineNumber)
{
lineNumber = AsmUtils.printLineNumber(mv, lineNumber);
if (isStatic)
{
mv.visitMethodInsn(INVOKESTATIC, clazz, method, "()V", false);
}
else
{
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, clazz, method, "()V", false);
}
return lineNumber;
}
public static void generateMethodInjection(ControllerClassData classData, ControllerMethodData methodData, MethodNode mv, boolean printMethods,
int lineNumber)
{
MethodDescription.InDefinedShape member = methodData.getMember();
boolean isStatic = member.isStatic();
if (printMethods)
{
lineNumber = printMethods(mv, classData.getType().getInternalName(), methodData.getBefore(), isStatic, lineNumber);
}
lineNumber = AsmUtils.printLineNumber(mv, lineNumber);
if (! isStatic)
{
mv.visitVarInsn(ALOAD, 0);
}
for (InjectValueData<?, Generic> valueData : methodData.getInjectValues())
{
if (isStatic)
{
mv.visitInsn(ACONST_NULL);
}
else
{
mv.visitVarInsn(ALOAD, 0);
}
AsmUtils.storeInt(mv, classData.getIndex());
AsmUtils.storeInt(mv, methodData.getIndex());
AsmUtils.storeInt(mv, valueData.getIndex());
mv.visitInsn(ICONST_0); // skip null checks in methods.
lineNumber = AsmUtils.printLineNumber(mv, lineNumber);
mv.visitMethodInsn(INVOKESTATIC, INJECTOR_CLASS, INJECTOR_METHOD, INJECTOR_METHOD_DESC, false);
TypeDescription paramType = valueData.getType().asErasure();
mv.visitTypeInsn(CHECKCAST, paramType.getInternalName()); // skip cast check?
}
lineNumber = AsmUtils.printLineNumber(mv, lineNumber);
if (isStatic)
{
mv.visitMethodInsn(INVOKESTATIC, classData.getType().getInternalName(), member.getName(), member.getDescriptor(), false);
}
else
{
mv.visitMethodInsn(INVOKESPECIAL, classData.getType().getInternalName(), member.getName(), member.getDescriptor(), false);
}
if (printMethods)
{
printMethods(mv, classData.getType().getInternalName(), methodData.getAfter(), isStatic, lineNumber);
}
}
}