blob: dd4488349b81bc4a56f3336a864a66a4f0118370 [file] [log] [blame]
// Copyright 2011 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.plastic;
import org.apache.tapestry5.internal.plastic.InstructionBuilderState.LVInfo;
import org.apache.tapestry5.internal.plastic.asm.Label;
import org.apache.tapestry5.internal.plastic.asm.MethodVisitor;
import org.apache.tapestry5.internal.plastic.asm.Opcodes;
import org.apache.tapestry5.internal.plastic.asm.Type;
import org.apache.tapestry5.plastic.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("rawtypes")
public class InstructionBuilderImpl extends Lockable implements Opcodes, InstructionBuilder
{
private static final int[] DUPE_OPCODES = new int[]
{DUP, DUP_X1, DUP_X2};
/**
* Maps from condition to opcode to jump to the false code block.
*/
private static final Map<Condition, Integer> conditionToOpcode = new HashMap<>();
static
{
Map<Condition, Integer> m = conditionToOpcode;
m.put(Condition.NULL, IFNONNULL);
m.put(Condition.NON_NULL, IFNULL);
m.put(Condition.ZERO, IFNE);
m.put(Condition.NON_ZERO, IFEQ);
m.put(Condition.EQUAL, IF_ICMPNE);
m.put(Condition.NOT_EQUAL, IF_ICMPEQ);
m.put(Condition.LESS_THAN, IF_ICMPGE);
m.put(Condition.GREATER, IF_ICMPLE);
}
private static final Map<String, Integer> typeToSpecialComparisonOpcode = new HashMap<>();
static
{
Map<String, Integer> m = typeToSpecialComparisonOpcode;
m.put("long", LCMP);
m.put("float", FCMPL);
m.put("double", DCMPL);
}
private static final Map<Object, Integer> constantOpcodes = new HashMap<>();
static
{
Map<Object, Integer> m = constantOpcodes;
m.put(Integer.valueOf(-1), ICONST_M1);
m.put(Integer.valueOf(0), ICONST_0);
m.put(Integer.valueOf(1), ICONST_1);
m.put(Integer.valueOf(2), ICONST_2);
m.put(Integer.valueOf(3), ICONST_3);
m.put(Integer.valueOf(4), ICONST_4);
m.put(Integer.valueOf(5), ICONST_5);
m.put(Long.valueOf(0), LCONST_0);
m.put(Long.valueOf(1), LCONST_1);
m.put(Float.valueOf(0), FCONST_0);
m.put(Float.valueOf(1), FCONST_1);
m.put(Float.valueOf(2), FCONST_2);
m.put(Double.valueOf(0), DCONST_0);
m.put(Double.valueOf(1), DCONST_1);
m.put(null, ACONST_NULL);
}
protected final InstructionBuilderState state;
protected final MethodVisitor v;
protected final NameCache cache;
InstructionBuilderImpl(MethodDescription description, MethodVisitor visitor, NameCache cache)
{
InstructionBuilderState state = new InstructionBuilderState(description, visitor, cache);
this.state = state;
// These are conveniences for values stored inside the state. In fact,
// these fields predate the InstructionBuilderState type.
this.v = state.visitor;
this.cache = state.nameCache;
}
@Override
public InstructionBuilder returnDefaultValue()
{
check();
PrimitiveType type = PrimitiveType.getByName(state.description.returnType);
if (type == null)
{
v.visitInsn(ACONST_NULL);
v.visitInsn(ARETURN);
} else
{
switch (type)
{
case VOID:
break;
case LONG:
v.visitInsn(LCONST_0);
break;
case FLOAT:
v.visitInsn(FCONST_0);
break;
case DOUBLE:
v.visitInsn(DCONST_0);
break;
default:
v.visitInsn(ICONST_0);
break;
}
v.visitInsn(type.returnOpcode);
}
return this;
}
@Override
public InstructionBuilder loadThis()
{
check();
v.visitVarInsn(ALOAD, 0);
return this;
}
@Override
public InstructionBuilder loadNull()
{
check();
v.visitInsn(ACONST_NULL);
return this;
}
@Override
public InstructionBuilder loadArgument(int index)
{
check();
PrimitiveType type = PrimitiveType.getByName(state.description.argumentTypes[index]);
int opcode = type == null ? ALOAD : type.loadOpcode;
v.visitVarInsn(state.argumentLoadOpcode[index], state.argumentIndex[index]);
return this;
}
@Override
public InstructionBuilder loadArguments()
{
check();
for (int i = 0; i < state.description.argumentTypes.length; i++)
{
loadArgument(i);
}
return this;
}
@Override
public InstructionBuilder invokeSpecial(String containingClassName, MethodDescription description)
{
check();
doInvoke(INVOKESPECIAL, containingClassName, description, false);
return this;
}
@Override
public InstructionBuilder invokeVirtual(PlasticMethod method)
{
check();
assert method != null;
MethodDescription description = method.getDescription();
return invokeVirtual(method.getPlasticClass().getClassName(), description.returnType, description.methodName,
description.argumentTypes);
}
@Override
public InstructionBuilder invokeVirtual(String className, String returnType, String methodName,
String... argumentTypes)
{
check();
doInvoke(INVOKEVIRTUAL, className, returnType, methodName, false, argumentTypes);
return this;
}
@Override
public InstructionBuilder invokeInterface(String interfaceName, String returnType, String methodName,
String... argumentTypes)
{
check();
doInvoke(INVOKEINTERFACE, interfaceName, returnType, methodName, true, argumentTypes);
return this;
}
private void doInvoke(int opcode, String className, String returnType, String methodName, boolean isInterface,
String... argumentTypes)
{
v.visitMethodInsn(opcode, cache.toInternalName(className), methodName,
cache.toMethodDescriptor(returnType, argumentTypes), isInterface);
}
@Override
public InstructionBuilder invokeStatic(Class clazz, Class returnType, String methodName, Class... argumentTypes)
{
doInvoke(INVOKESTATIC, clazz, returnType, methodName, false, argumentTypes);
return this;
}
private void doInvoke(int opcode, Class clazz, Class returnType, String methodName, boolean isInterface,
Class... argumentTypes)
{
doInvoke(opcode, clazz.getName(), cache.toTypeName(returnType), methodName, isInterface,
PlasticUtils.toTypeNames(argumentTypes));
}
@Override
public InstructionBuilder invoke(Method method)
{
check();
return invoke(method.getDeclaringClass(), method.getReturnType(), method.getName(), method.getParameterTypes());
}
@Override
public InstructionBuilder invoke(Class clazz, Class returnType, String methodName, Class... argumentTypes)
{
check();
doInvoke(clazz.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, clazz, returnType, methodName,
clazz.isInterface(), argumentTypes);
return this;
}
private void doInvoke(int opcode, String containingClassName, MethodDescription description, boolean isInterface)
{
v.visitMethodInsn(opcode, cache.toInternalName(containingClassName), description.methodName,
cache.toDesc(description), isInterface);
}
@Override
public InstructionBuilder returnResult()
{
check();
PrimitiveType type = PrimitiveType.getByName(state.description.returnType);
int opcode = type == null ? ARETURN : type.returnOpcode;
v.visitInsn(opcode);
return this;
}
@Override
public InstructionBuilder boxPrimitive(String typeName)
{
check();
PrimitiveType type = PrimitiveType.getByName(typeName);
if (type != null && type != PrimitiveType.VOID)
{
v.visitMethodInsn(INVOKESTATIC, type.wrapperInternalName, "valueOf", type.valueOfMethodDescriptor,
false);
}
return this;
}
@Override
public InstructionBuilder unboxPrimitive(String typeName)
{
check();
PrimitiveType type = PrimitiveType.getByName(typeName);
if (type != null)
{
doUnbox(type);
}
return this;
}
private void doUnbox(PrimitiveType type)
{
v.visitMethodInsn(INVOKEVIRTUAL, type.wrapperInternalName, type.toValueMethodName, type.toValueMethodDescriptor,
false);
}
@Override
public InstructionBuilder getField(String className, String fieldName, String typeName)
{
check();
v.visitFieldInsn(GETFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName));
return this;
}
@Override
public InstructionBuilder getStaticField(String className, String fieldName, String typeName)
{
check();
v.visitFieldInsn(GETSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName));
return this;
}
@Override
public InstructionBuilder getStaticField(String className, String fieldName, Class fieldType)
{
check();
return getStaticField(className, fieldName, cache.toTypeName(fieldType));
}
@Override
public InstructionBuilder putStaticField(String className, String fieldName, Class fieldType)
{
check();
return putStaticField(className, fieldName, cache.toTypeName(fieldType));
}
@Override
public InstructionBuilder putStaticField(String className, String fieldName, String typeName)
{
check();
v.visitFieldInsn(PUTSTATIC, cache.toInternalName(className), fieldName, cache.toDesc(typeName));
return this;
}
@Override
public InstructionBuilder getField(PlasticField field)
{
check();
return getField(field.getPlasticClass().getClassName(), field.getName(), field.getTypeName());
}
@Override
public InstructionBuilder putField(String className, String fieldName, String typeName)
{
check();
v.visitFieldInsn(PUTFIELD, cache.toInternalName(className), fieldName, cache.toDesc(typeName));
return this;
}
@Override
public InstructionBuilder putField(String className, String fieldName, Class fieldType)
{
check();
return putField(className, fieldName, cache.toTypeName(fieldType));
}
@Override
public InstructionBuilder getField(String className, String fieldName, Class fieldType)
{
check();
return getField(className, fieldName, cache.toTypeName(fieldType));
}
@Override
public InstructionBuilder loadArrayElement(int index, String elementType)
{
check();
loadConstant(index);
PrimitiveType type = PrimitiveType.getByName(elementType);
if (type == null)
{
v.visitInsn(AALOAD);
} else
{
throw new RuntimeException("Access to non-object arrays is not yet supported.");
}
return this;
}
@Override
public InstructionBuilder loadArrayElement()
{
check();
v.visitInsn(AALOAD);
return this;
}
@Override
public InstructionBuilder checkcast(String className)
{
check();
// Found out the hard way that array names are handled differently; you cast to the descriptor, not the internal
// name.
String internalName = className.contains("[") ? cache.toDesc(className) : cache.toInternalName(className);
v.visitTypeInsn(CHECKCAST, internalName);
return this;
}
@Override
public InstructionBuilder checkcast(Class clazz)
{
check();
return checkcast(cache.toTypeName(clazz));
}
@Override
public InstructionBuilder startTryCatch(TryCatchCallback callback)
{
check();
new TryCatchBlockImpl(this, state).doCallback(callback);
return this;
}
@Override
public InstructionBuilder newInstance(String className)
{
check();
v.visitTypeInsn(NEW, cache.toInternalName(className));
return this;
}
@Override
public InstructionBuilder newInstance(Class clazz)
{
check();
return newInstance(clazz.getName());
}
@Override
public InstructionBuilder invokeConstructor(String className, String... argumentTypes)
{
check();
doInvoke(INVOKESPECIAL, className, "void", "<init>", false, argumentTypes);
return this;
}
@Override
public InstructionBuilder invokeConstructor(Class clazz, Class... argumentTypes)
{
check();
return invokeConstructor(clazz.getName(), PlasticUtils.toTypeNames(argumentTypes));
}
@Override
public InstructionBuilder dupe(int depth)
{
check();
if (depth < 0 || depth >= DUPE_OPCODES.length)
throw new IllegalArgumentException(String.format(
"Dupe depth %d is invalid; values from 0 to %d are allowed.", depth, DUPE_OPCODES.length - 1));
v.visitInsn(DUPE_OPCODES[depth]);
return this;
}
@Override
public InstructionBuilder dupe()
{
check();
v.visitInsn(DUP);
return this;
}
@Override
public InstructionBuilder pop()
{
check();
v.visitInsn(POP);
return this;
}
@Override
public InstructionBuilder swap()
{
check();
v.visitInsn(SWAP);
return this;
}
@Override
public InstructionBuilder loadConstant(Object constant)
{
check();
Integer opcode = constantOpcodes.get(constant);
if (opcode != null)
v.visitInsn(opcode);
else
v.visitLdcInsn(constant);
return this;
}
@Override
public InstructionBuilder loadTypeConstant(String typeName)
{
check();
Type type = Type.getType(cache.toDesc(typeName));
v.visitLdcInsn(type);
return this;
}
@Override
public InstructionBuilder loadTypeConstant(Class clazz)
{
check();
Type type = Type.getType(clazz);
v.visitLdcInsn(type);
return this;
}
@Override
public InstructionBuilder castOrUnbox(String typeName)
{
check();
PrimitiveType type = PrimitiveType.getByName(typeName);
if (type == null)
return checkcast(typeName);
v.visitTypeInsn(CHECKCAST, type.wrapperInternalName);
doUnbox(type);
return this;
}
@Override
public InstructionBuilder throwException(String className, String message)
{
check();
newInstance(className).dupe().loadConstant(message);
invokeConstructor(className, "java.lang.String");
v.visitInsn(ATHROW);
return this;
}
@Override
public InstructionBuilder throwException(Class<? extends Throwable> exceptionType, String message)
{
check();
return throwException(cache.toTypeName(exceptionType), message);
}
@Override
public InstructionBuilder throwException()
{
check();
v.visitInsn(ATHROW);
return this;
}
@Override
public InstructionBuilder startSwitch(int min, int max, SwitchCallback callback)
{
check();
assert callback != null;
new SwitchBlockImpl(this, state, min, max).doCallback(callback);
return this;
}
@Override
public InstructionBuilder startVariable(String type, final LocalVariableCallback callback)
{
check();
final LocalVariable var = state.startVariable(type);
callback.doBuild(var, this);
state.stopVariable(var);
return this;
}
@Override
public InstructionBuilder storeVariable(LocalVariable var)
{
check();
state.store(var);
return this;
}
@Override
public InstructionBuilder loadVariable(LocalVariable var)
{
check();
state.load(var);
return this;
}
@Override
public InstructionBuilder when(Condition condition, final InstructionBuilderCallback ifTrue)
{
check();
assert ifTrue != null;
// This is nice for code coverage but could be more efficient, possibly generate
// more efficient bytecode, if it talked to the v directly.
return when(condition, new WhenCallback()
{
@Override
public void ifTrue(InstructionBuilder builder)
{
ifTrue.doBuild(builder);
}
@Override
public void ifFalse(InstructionBuilder builder)
{
}
});
}
@Override
public InstructionBuilder when(Condition condition, final WhenCallback callback)
{
check();
assert condition != null;
assert callback != null;
Label ifFalseLabel = new Label();
Label endIfLabel = new Label();
v.visitJumpInsn(conditionToOpcode.get(condition), ifFalseLabel);
callback.ifTrue(this);
v.visitJumpInsn(GOTO, endIfLabel);
v.visitLabel(ifFalseLabel);
callback.ifFalse(this);
v.visitLabel(endIfLabel);
return this;
}
@Override
public InstructionBuilder doWhile(Condition condition, final WhileCallback callback)
{
check();
assert condition != null;
assert callback != null;
Label doCheck = state.newLabel();
Label exitLoop = new Label();
callback.buildTest(this);
v.visitJumpInsn(conditionToOpcode.get(condition), exitLoop);
callback.buildBody(this);
v.visitJumpInsn(GOTO, doCheck);
v.visitLabel(exitLoop);
return this;
}
@Override
public InstructionBuilder increment(LocalVariable variable)
{
check();
LVInfo info = state.locals.get(variable);
v.visitIincInsn(info.offset, 1);
return this;
}
@Override
public InstructionBuilder arrayLength()
{
check();
v.visitInsn(ARRAYLENGTH);
return this;
}
@Override
public InstructionBuilder iterateArray(final InstructionBuilderCallback callback)
{
startVariable("int", new LocalVariableCallback()
{
@Override
public void doBuild(final LocalVariable indexVariable, InstructionBuilder builder)
{
builder.loadConstant(0).storeVariable(indexVariable);
builder.doWhile(Condition.LESS_THAN, new WhileCallback()
{
@Override
public void buildTest(InstructionBuilder builder)
{
builder.dupe().arrayLength();
builder.loadVariable(indexVariable).swap();
}
@Override
public void buildBody(InstructionBuilder builder)
{
builder.dupe().loadVariable(indexVariable).loadArrayElement();
callback.doBuild(builder);
builder.increment(indexVariable);
}
});
}
});
return this;
}
@Override
public InstructionBuilder dupeWide()
{
check();
v.visitInsn(DUP2);
return this;
}
@Override
public InstructionBuilder popWide()
{
check();
v.visitInsn(POP2);
return this;
}
@Override
public InstructionBuilder compareSpecial(String typeName)
{
check();
Integer opcode = typeToSpecialComparisonOpcode.get(typeName);
if (opcode == null)
throw new IllegalArgumentException(String.format("Not a special primitive type: '%s'.", typeName));
v.visitInsn(opcode);
return this;
}
void doCallback(InstructionBuilderCallback callback)
{
check();
if (callback != null)
callback.doBuild(this);
lock();
}
}