/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat and individual contributors
* by the @authors tag.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.jokre.transformer;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import static org.jboss.jokre.transformer.MapAdapterConstants.*;
import java.util.List;
/**
* Adapter used to transform calls to Map.put into a potentially more efficient implementation
*/
public class MapPutCallAdapter extends ClassAdapter
{
private ClassLoader loader;
private List<String> methodNames;
private boolean transformed;
public MapPutCallAdapter(ClassVisitor cv, ClassLoader loader, List<String> methodNames)
{
super(cv);
this.loader = loader;
this.methodNames = methodNames;
this.transformed = false;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (methodNames.contains(name))
{
// TODO -- see if we really need to use a JSR inliner
MapPutCallMethodAdapter adapter = new MapPutCallMethodAdapter(mv);
//return adapter;
MethodVisitor inliner = new JSRInlinerAdapter(adapter, access, name, desc, signature, exceptions);
return inliner;
// return new MapPutMethodAdapter(inliner, access, name, desc, signature, exceptions);
} else {
//return mv;
MethodVisitor inliner = new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions);
return inliner;
}
}
public boolean isTransformed() {
return transformed;
}
/**
* method adapter which identifies Map.put calls and transforms them
*/
public class MapPutCallMethodAdapter extends MethodAdapter
{
private boolean isPending;
private int pendingOpcode;
private String pendingOwner;
public MapPutCallMethodAdapter(MethodVisitor mv)
{
super(mv);
isPending = false;
pendingOwner = null;
pendingOpcode = 0;
}
/**
* if a put call was pending from the last instruction then generate it now
* throwing away however many operands ar appropriate
*
* @return true if there was a pending call otherwise false
*/
public boolean generatePending(boolean throwAway)
{
if (!isPending) {
return false;
}
// clear the pending flag before we generate the required instructions
isPending = false;
transformed = true;
// generate the required put call sequence if a put call is pending and
// then clear the pending flag
Label l1 = new Label();
Label l2 = new Label();
// [... map, key, value] ==> [... key, value, map, key, value]
super.visitInsn(Opcodes.DUP2_X1);
// [... key, value, map, key, value] ==> [... key, value, map]
super.visitInsn(Opcodes.POP2);
// [... key, value, map] ==> [... key, value, map, map]
super.visitInsn(Opcodes.DUP);
// [... key, value, map, map] ==> [... key, value, map, bool]
super.visitTypeInsn(Opcodes.INSTANCEOF, CLASS_NON_RETURN_MAP);
// [... key, value, map, bool] ==> [... key, value, map]
super.visitJumpInsn(Opcodes.IFEQ, l1);
// [... key, value, map ] ==> [... key, value, map]
super.visitTypeInsn(Opcodes.CHECKCAST, CLASS_NON_RETURN_MAP);
// [... key, value, map ] ==> [... map, key, value, map]
super.visitInsn(Opcodes.DUP_X2);
// [... map, key, value, map ] ==> [... map, key, value]
super.visitInsn(Opcodes.POP);
if (throwAway) {
// [... map, key, value] ==> [...]
super.visitMethodInsn(Opcodes.INVOKEINTERFACE, CLASS_NON_RETURN_MAP, PUT_METHOD_FAST_PATH_NAME, SET_METHOD_DESC);
} else {
// [... map, key, value] ==> [... retvalue]
super.visitMethodInsn(Opcodes.INVOKEINTERFACE, CLASS_NON_RETURN_MAP, PUT_METHOD_ALTERNATIVE_SLOW_PATH_NAME, PUT_METHOD_DESC);
}
super.visitJumpInsn(Opcodes.GOTO, l2);
super.visitLabel(l1);
// [... key, value, map ] ==> [... map, key, value, map]
super.visitInsn(Opcodes.DUP_X2);
// [... map, key, value, map ] ==> [... map, key, value]
super.visitInsn(Opcodes.POP);
// [... map, key, value] ==> [..., retval]
super.visitMethodInsn(pendingOpcode, pendingOwner, PUT_METHOD_NAME, PUT_METHOD_DESC);
// [..., retval] ==> [...]
if (throwAway) {
super.visitInsn(Opcodes.POP);
}
super.visitLabel(l2);
// we return true here to indicate that the pop has been taken care of.
// if the caller was going to generate a pop then this will inhibit it.
return true;
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.POP) {
if (!generatePending(true)) {
super.visitInsn(opcode);
}
} else {
generatePending(false);
super.visitInsn(opcode);
}
}
@Override
public void visitIntInsn(int opcode, int operand) {
generatePending(false);
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int var) {
generatePending(false);
super.visitVarInsn(opcode, var);
}
@Override
public void visitTypeInsn(int opcode, String desc) {
generatePending(false);
super.visitTypeInsn(opcode, desc);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
generatePending(false);
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
generatePending(false);
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLdcInsn(Object cst) {
generatePending(false);
super.visitLdcInsn(cst);
}
@Override
public void visitIincInsn(int var, int increment) {
generatePending(false);
super.visitIincInsn(var, increment);
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
generatePending(false);
super.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
generatePending(false);
super.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
generatePending(false);
super.visitMultiANewArrayInsn(desc, dims);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
generatePending(false);
// we are interested in cases where the call is to java.util.Map.put() or
// java.util.concurrent.ConcurrentMap.put(). These are the only two map
// interfaces the Infinispan caches implement. We also want to replace
// direct calls to org.infinispan.Cache.put() or to any class which
// implements org.infinispan.Cache.put()
if (!name.equals(PUT_METHOD_NAME) || !desc.equals(PUT_METHOD_DESC)) {
super.visitMethodInsn(opcode, owner, name, desc);
return;
}
switch(opcode) {
case Opcodes.INVOKEINTERFACE:
{
if (owner.equals(CLASS_MAP) ||
owner.equals(CLASS_CONCURRENT_MAP) ||
owner.equals(CLASS_CACHE) ||
owner.equals(CLASS_ADVANCED_CACHE))
{
// delay generation of the put call until we see the next instruction
isPending = true;
pendingOwner = owner;
pendingOpcode = opcode;
return;
}
}
break;
case Opcodes.INVOKEVIRTUAL:
{
if (isNonReturnMap(owner)) {
// delay generation of the put call until we see the next instruction
isPending = true;
pendingOwner = owner;
pendingOpcode = opcode;
return;
}
break;
}
}
// ok generate the call now
super.visitMethodInsn(opcode, owner, name, desc);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// high water mark is 2 higher when we generate the transformed call
// if (methodTransformed) {
// maxStack += 2;
// }
super.visitMaxs(maxStack, maxLocals);
}
private boolean isNonReturnMap(String owner)
{
// TODO we use a class load here for now but we may need to avoid this later
Class<?> ownerClass;
try {
ownerClass = loader.loadClass(owner.replace('/', '.'));
} catch (Exception e) {
return false;
}
return isInfinispanCacheClass(ownerClass);
}
private boolean isInfinispanCacheClass(Class<?> candidate)
{
// n.b. reflection gives us class names in external format
if (candidate.getName().equals(MapAdapterConstants.CLASS_NON_RETURN_MAP_EXTERNAL)) {
return true;
}
// check local interfaces
Class<?> interfaces[] = candidate.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (isInfinispanCacheClass(interfaces[i])) {
return true;
}
}
Class<?> superclass = candidate.getSuperclass();
if (superclass != Object.class && superclass != null) {
return isInfinispanCacheClass(superclass);
}
return false;
}
}
}