package net.sourceforge.javautil.bytecode.impl.asm;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.javautil.bytecode.BytecodeException;
import net.sourceforge.javautil.bytecode.api.IBytecodeField;
import net.sourceforge.javautil.bytecode.api.IBytecodeReferenceable;
import net.sourceforge.javautil.bytecode.api.IBytecodeResolvable;
import net.sourceforge.javautil.bytecode.api.IBytecodeWriter;
import net.sourceforge.javautil.bytecode.api.LiteralValue;
import net.sourceforge.javautil.bytecode.api.MethodDescriptor;
import net.sourceforge.javautil.bytecode.api.TypeDescriptor;
import net.sourceforge.javautil.bytecode.api.IBytecodeResolvable.ClassType;
import net.sourceforge.javautil.bytecode.api.event.BytecodeEvent;
import net.sourceforge.javautil.bytecode.api.type.AbstractType;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeBlock;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeConstructorBase;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeLocalVariableDeclaration;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeMarker;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeMethod;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeMethodAbstract;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeMethodConcrete;
import net.sourceforge.javautil.bytecode.api.type.method.BytecodeScope;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeWriterMethod;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeWriterMethod.ComparisonOperator;
import net.sourceforge.javautil.bytecode.api.type.method.IBytecodeWriterMethod.MathmaticalOperator;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* A standard ASM byte code writer implementation.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class BytecodeWriterMethodASM implements IBytecodeWriterMethod<BytecodeContextMethodASM>, Opcodes {
public void begin(BytecodeContextMethodASM context) {
context.getTarget().visitCode();
}
public void end(BytecodeContextMethodASM context) {
this.mark(context, context.getScope(0).getEnd());
for (BytecodeLocalVariableDeclaration lv : context.getLocalVariables()) {
context.getTarget().visitLocalVariable(lv.getName(), lv.getType().toDescriptorString(), null,
((BytecodeMarkerLabel)lv.getScope().getStart()).label,
((BytecodeMarkerLabel)lv.getScope().getEnd()).label,
lv.getIndex()
);
}
context.getTarget().visitMaxs(context.getMaxStackSize(), context.getMaxLocalVariables());
context.getTarget().visitEnd();
}
public void pop(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(POP);
}
public void arrayLength(BytecodeContextMethodASM context) { context.getTarget().visitInsn(ARRAYLENGTH); }
public void arrayLoad(BytecodeContextMethodASM context, TypeDescriptor type) {
context.getTarget().visitInsn(AALOAD);
context.popStack();
context.popStack();
context.push(type);
}
public void arrayStore(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(AASTORE);
context.popStack();
context.popStack();
}
public void box(BytecodeContextMethodASM context) {
TypeDescriptor type = context.getStack(0);
if (!type.isPrimitive()) throw new BytecodeException("Cannot box a non-primitive: " + type);
//Type unboxed = getType(type);
//context.getTarget().box(unboxed);
context.pop(type);
context.push(type.getBoxedType());
}
public void unbox(BytecodeContextMethodASM context) {
TypeDescriptor type = context.getStack(0);
if (type.isPrimitive()) throw new BytecodeException("Cannot unbox a primitive: " + type);
//Type boxed = getType(type);
//context.getTarget().unbox(boxed);
context.pop(type);
context.push(type.getUnboxedType());
}
public void cast(BytecodeContextMethodASM context, TypeDescriptor from, TypeDescriptor to) {
context.pop(from);
context.push(to);
}
public void catchException(BytecodeContextMethodASM context, IBytecodeMarker start, IBytecodeMarker end, TypeDescriptor exceptionType) {
context.getTarget().visitTryCatchBlock(
((BytecodeMarkerLabel)start).label, ((BytecodeMarkerLabel)end).label,
this.mark(context).label, exceptionType == null ? null : exceptionType.toDescriptorString()
);
}
public void checkCast(BytecodeContextMethodASM context, TypeDescriptor type) {
context.getTarget().visitTypeInsn(Opcodes.CHECKCAST, type.getName());
}
public void dup(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(DUP);
context.push(context.getStack(0));
}
public void dup2(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(DUP2);
context.push(context.getStack(0));
}
public void doMath(BytecodeContextMethodASM context, IBytecodeReferenceable value1, IBytecodeReferenceable value2, MathmaticalOperator operator) {
value1.load(context);
value2.load(context);
Type type = getType(value1.getType());
context.push(value1.getType());
context.push(value2.getType());
int opcode = 0;
switch (operator) {
case Multiply: opcode = type.getOpcode(IMUL); break;
case Divide: opcode = type.getOpcode(IDIV); break;
case Plus: opcode = type.getOpcode(IADD); break;
case Minus: opcode = type.getOpcode(ISUB); break;
default:
throw new BytecodeException("Unsupported operator: " + operator);
}
context.popStack();
context.popStack();
context.getTarget().visitInsn(opcode);
context.push(value1.getType());
}
public void loadNull(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(ACONST_NULL);
}
@Override public void loadType(BytecodeContextMethodASM context, IBytecodeResolvable type) {
context.getTarget().visitLdcInsn(Type.getType(type.getType().toDescriptorString()));
}
public void loadLiteral(BytecodeContextMethodASM context, LiteralValue literal) {
Type type = getType(literal.getType());
if (literal.getValue() instanceof Number || literal.getValue() instanceof Character) {
Number value = literal.getValue() instanceof Character ? (int) ((Character)literal.getValue()).charValue() : (Number) literal.getValue();
if (value instanceof Double || value instanceof Float || value.longValue() > Short.MAX_VALUE) {
context.getTarget().visitLdcInsn(value);
} else {
int i = value.intValue();
if (i <= 5) context.getTarget().visitInsn(ICONST_0 + i);
else if (i <= Byte.MAX_VALUE) context.getTarget().visitIntInsn(BIPUSH, i);
else context.getTarget().visitIntInsn(SIPUSH, i);
}
} else if (type == Type.BOOLEAN_TYPE) {
boolean value = (Boolean) literal.getValue();
context.getTarget().visitInsn(value ? ICONST_1 : ICONST_0);
} else {
context.getTarget().visitLdcInsn(literal.getValue());
}
context.push(literal.getType());
}
public void storeLocal (BytecodeContextMethodASM context, int idx) {
Type type = getType(context.getLocalVariable(idx).getType());
context.getTarget().visitVarInsn(type.getOpcode(ISTORE), idx);
context.popStack();
}
public void loadLocal (BytecodeContextMethodASM context, int idx) {
TypeDescriptor ts = context.getLocalVariable(idx).getType();
Type type = getType(ts);
context.getTarget().visitVarInsn(type.getOpcode(ILOAD), idx);
context.push(ts);
}
public void loadField(BytecodeContextMethodASM context, IBytecodeResolvable type, String name) {
IBytecodeField field = type.getField(context.getResolutionPool(), name);
if (field == null) throw new BytecodeException("No such field " + name + " for " + type.getType().getClassName());
context.getTarget().visitFieldInsn(GETFIELD, type.getType().getName(), name, field.getType().toDescriptorString());
context.popStack();
context.push(field.getType());
}
public void loadStaticField(BytecodeContextMethodASM context, IBytecodeResolvable type, String name) {
IBytecodeField field = type.getField(context.getResolutionPool(), name);
if (field == null) throw new BytecodeException("No such field " + name + " for " + type.getType().getClassName());
context.getTarget().visitFieldInsn(GETSTATIC, type.getType().getName(), name, field.getType().toDescriptorString());
context.push(field.getType());
}
public void storeField(BytecodeContextMethodASM context, IBytecodeResolvable type, String name) {
IBytecodeField field = type.getField(context.getResolutionPool(), name);
if (field == null) throw new BytecodeException("No such field " + name + " for " + type.getType().getClassName());
context.getTarget().visitFieldInsn(PUTFIELD, type.getType().getName(), name, field.getType().toDescriptorString());
context.popStack();
context.popStack();
}
public void storeStaticField(BytecodeContextMethodASM context, IBytecodeResolvable type, String name) {
IBytecodeField field = type.getField(context.getResolutionPool(), name);
if (field == null) throw new BytecodeException("No such field " + name + " for " + type.getType().getClassName());
context.getTarget().visitFieldInsn(PUTSTATIC, type.getType().getName(), name, field.getType().toDescriptorString());
context.popStack();
}
public void newArray(BytecodeContextMethodASM context, TypeDescriptor type, IBytecodeReferenceable... indices) {
for (IBytecodeReferenceable length : indices) length.load(context);
if (indices.length == 1) {
context.getTarget().visitTypeInsn(ANEWARRAY, type.getName());
context.push(type.getArrayType(1));
} else {
TypeDescriptor atype = type.getArrayType(indices.length);
context.getTarget().visitMultiANewArrayInsn(atype.getName(), indices.length);
context.push(atype);
}
}
public void newInstance (BytecodeContextMethodASM context, TypeDescriptor typeDescriptor) {
context.getTarget().visitTypeInsn(NEW, typeDescriptor.getName());
context.push(typeDescriptor);
}
public void throwException(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(ATHROW);
}
public void invoke(BytecodeContextMethodASM context, IBytecodeResolvable type, IBytecodeMethod method) {
String desc = method.getDescriptor().toDescriptorString();
int opcode = Opcodes.INVOKEVIRTUAL;
if ("<init>".equals(method.getName())) opcode = Opcodes.INVOKESPECIAL;
else if (method.getAccess().isStatic()) opcode = Opcodes.INVOKESTATIC;
else if (method.getDeclaringType().compareTo(type) != 0) opcode = Opcodes.INVOKESPECIAL;
else if (method.getDeclaringType().getClassType() == ClassType.Interface) opcode = Opcodes.INVOKEINTERFACE;
context.getTarget().visitMethodInsn(opcode, method.getDeclaringType().getType().getName(), method.getName(), desc);
if (opcode != Opcodes.INVOKESTATIC) context.popStack();
for (int i=0; i<method.getDescriptor().getParameters().length; i++) context.popStack();
TypeDescriptor rt = method.getDescriptor().getReturnType();
if (rt != null && rt != TypeDescriptor.VOID) context.push(rt);
}
public void returnValue(BytecodeContextMethodASM context, TypeDescriptor td) {
if (td == null) {
this.loadNull(context);
context.getTarget().visitInsn(ARETURN);
} else {
Type type = getType(td);
context.getTarget().visitInsn(type.getOpcode(IRETURN));
}
}
public void returnVoid(BytecodeContextMethodASM context) {
context.getTarget().visitInsn(RETURN);
}
public void jump(BytecodeContextMethodASM context, IBytecodeMarker marker) {
context.getTarget().visitJumpInsn(GOTO, ((BytecodeMarkerLabel)marker).label);
}
public void jumpIf(BytecodeContextMethodASM context, IBytecodeReferenceable arg1, IBytecodeReferenceable arg2, ComparisonOperator operator, boolean negate, IBytecodeMarker marker) {
arg1.load(context);
arg2.load(context);
context.getTarget().visitJumpInsn(this.getIfOpcode(arg1.getType(), arg2.getType(), operator, negate), ((BytecodeMarkerLabel)marker).label);
}
public void jumpIfException(BytecodeContextMethodASM context, IBytecodeMarker codeStart, IBytecodeMarker codeEnd, IBytecodeMarker handler, TypeDescriptor type) {
context.getTarget().visitTryCatchBlock(getLabel(codeStart), getLabel(codeEnd), getLabel(handler), type.getName());
}
public void jumpFinally(BytecodeContextMethodASM context, IBytecodeMarker codeStart, IBytecodeMarker finallyMarker) {
context.getTarget().visitTryCatchBlock(getLabel(codeStart), getLabel(finallyMarker), getLabel(finallyMarker), null);
}
public BytecodeMarkerLabel createMarker(BytecodeContextMethodASM context) { return new BytecodeMarkerLabel(); }
public BytecodeMarkerLabel mark(BytecodeContextMethodASM context) {
BytecodeMarkerLabel label = new BytecodeMarkerLabel();
label.write(context);
return label;
}
public void mark(BytecodeContextMethodASM context, IBytecodeMarker marker) { marker.write(context); }
public void handle(BytecodeEvent evt) {
}
/**
* @param marker The original marker
* @return The ASM marker
*/
protected Label getLabel (IBytecodeMarker marker) {
return ((BytecodeMarkerLabel)marker).label;
}
/**
* @param td The type descriptor
* @return The corresponding type
*/
protected Type getType (TypeDescriptor td) { return Type.getType(td.toDescriptorString()); }
/**
* @param arg1 The first argument
* @param arg2 The second argument
* @param comparator The comparator to use
* @param negate True if the comparison is negated
* @return The corresponding opcode to use
*/
protected int getIfOpcode (TypeDescriptor arg1, TypeDescriptor arg2, ComparisonOperator comparator, boolean negate) {
if (arg1 == TypeDescriptor.INTEGER && arg2 == TypeDescriptor.INTEGER) {
switch (comparator) {
case Equals: return negate ? IF_ICMPNE : IF_ICMPEQ;
case GreaterThan: return negate ? IF_ICMPLE : IF_ICMPGT;
case LesserThan: return negate ? IF_ICMPGE : IF_ICMPLT;
case GreaterEqual: return negate ? IF_ICMPLT : IF_ICMPGE;
case LesserEqual: return negate ? IF_ICMPGT : IF_ICMPLE;
}
} else if (arg2 == null) {
return negate ? IFNONNULL : IFNULL;
} else if (arg1.isPrimitive() && arg2.isPrimitive()) {
switch (comparator) {
case Equals: return negate ? IFNE : IFEQ;
case GreaterThan: return negate ? IFLE : IFGT;
case LesserThan: return negate ? IFGE : IFLT;
case GreaterEqual: return negate ? IFLT : IFGE;
case LesserEqual: return negate ? IFGT : IFLE;
}
} else {
switch (comparator) {
case Equals: return negate ? IF_ACMPNE : IF_ACMPEQ;
}
}
throw new BytecodeException("Unsupported operator type for comparison arguments: " + comparator);
}
}